Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M .gitattributes
1111
#   Disable line ending normalize on checkin.
1212
13
*.bin binary
14
*.exe binary
1315
*.gif binary
1416
*.jar binary
A font-names
1
#!/usr/bin/env bash
2
3
# Writes the name for all OTF files found in the current directory or lower
4
5
find src/main/resources/fonts -type f \( -name "*otf" -o -name "*ttf" \) -exec \
6
  fc-scan --format "%{foundry}: %{family}\n" {} \; | uniq | sort
7
18
M installer
1515
ARG_JAVA_OS="linux"
1616
ARG_JAVA_ARCH="amd64"
17
ARG_JAVA_VERSION="15.0.1"
18
ARG_JAVA_UPDATE="9"
17
ARG_JAVA_VERSION="15.0.2"
18
ARG_JAVA_UPDATE="10"
1919
ARG_JAVA_DIR="java"
2020
A licenses/fonts/SOURCE-SERIF-4.md
1
Copyright 2014-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
2
3
This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
5
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
6
7
8
-----------------------------------------------------------
9
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
-----------------------------------------------------------
11
12
PREAMBLE
13
The goals of the Open Font License (OFL) are to stimulate worldwide
14
development of collaborative font projects, to support the font creation
15
efforts of academic and linguistic communities, and to provide a free and
16
open framework in which fonts may be shared and improved in partnership
17
with others.
18
19
The OFL allows the licensed fonts to be used, studied, modified and
20
redistributed freely as long as they are not sold by themselves. The
21
fonts, including any derivative works, can be bundled, embedded,
22
redistributed and/or sold with any software provided that any reserved
23
names are not used by derivative works. The fonts and derivatives,
24
however, cannot be released under any other type of license. The
25
requirement for fonts to remain under this license does not apply
26
to any document created using the fonts or their derivatives.
27
28
DEFINITIONS
29
"Font Software" refers to the set of files released by the Copyright
30
Holder(s) under this license and clearly marked as such. This may
31
include source files, build scripts and documentation.
32
33
"Reserved Font Name" refers to any names specified as such after the
34
copyright statement(s).
35
36
"Original Version" refers to the collection of Font Software components as
37
distributed by the Copyright Holder(s).
38
39
"Modified Version" refers to any derivative made by adding to, deleting,
40
or substituting -- in part or in whole -- any of the components of the
41
Original Version, by changing formats or by porting the Font Software to a
42
new environment.
43
44
"Author" refers to any designer, engineer, programmer, technical
45
writer or other person who contributed to the Font Software.
46
47
PERMISSION & CONDITIONS
48
Permission is hereby granted, free of charge, to any person obtaining
49
a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
redistribute, and sell modified and unmodified copies of the Font
51
Software, subject to the following conditions:
52
53
1) Neither the Font Software nor any of its individual components,
54
in Original or Modified Versions, may be sold by itself.
55
56
2) Original or Modified Versions of the Font Software may be bundled,
57
redistributed and/or sold with any software, provided that each copy
58
contains the above copyright notice and this license. These can be
59
included either as stand-alone text files, human-readable headers or
60
in the appropriate machine-readable metadata fields within text or
61
binary files as long as those fields can be easily viewed by the user.
62
63
3) No Modified Version of the Font Software may use the Reserved Font
64
Name(s) unless explicit written permission is granted by the corresponding
65
Copyright Holder. This restriction only applies to the primary font name as
66
presented to the users.
67
68
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
Software shall not be used to promote, endorse or advertise any
70
Modified Version, except to acknowledge the contribution(s) of the
71
Copyright Holder(s) and the Author(s) or with their explicit written
72
permission.
73
74
5) The Font Software, modified or unmodified, in part or in whole,
75
must be distributed entirely under this license, and must not be
76
distributed under any other license. The requirement for fonts to
77
remain under this license does not apply to any document created
78
using the Font Software.
79
80
TERMINATION
81
This license becomes null and void if any of the above conditions are
82
not met.
83
84
DISCLAIMER
85
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
OTHER DEALINGS IN THE FONT SOFTWARE.
194
D licenses/fonts/SOURCE-SERIF-PRO.md
1
Copyright 2014-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
2
3
This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
5
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
6
7
8
-----------------------------------------------------------
9
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
-----------------------------------------------------------
11
12
PREAMBLE
13
The goals of the Open Font License (OFL) are to stimulate worldwide
14
development of collaborative font projects, to support the font creation
15
efforts of academic and linguistic communities, and to provide a free and
16
open framework in which fonts may be shared and improved in partnership
17
with others.
18
19
The OFL allows the licensed fonts to be used, studied, modified and
20
redistributed freely as long as they are not sold by themselves. The
21
fonts, including any derivative works, can be bundled, embedded,
22
redistributed and/or sold with any software provided that any reserved
23
names are not used by derivative works. The fonts and derivatives,
24
however, cannot be released under any other type of license. The
25
requirement for fonts to remain under this license does not apply
26
to any document created using the fonts or their derivatives.
27
28
DEFINITIONS
29
"Font Software" refers to the set of files released by the Copyright
30
Holder(s) under this license and clearly marked as such. This may
31
include source files, build scripts and documentation.
32
33
"Reserved Font Name" refers to any names specified as such after the
34
copyright statement(s).
35
36
"Original Version" refers to the collection of Font Software components as
37
distributed by the Copyright Holder(s).
38
39
"Modified Version" refers to any derivative made by adding to, deleting,
40
or substituting -- in part or in whole -- any of the components of the
41
Original Version, by changing formats or by porting the Font Software to a
42
new environment.
43
44
"Author" refers to any designer, engineer, programmer, technical
45
writer or other person who contributed to the Font Software.
46
47
PERMISSION & CONDITIONS
48
Permission is hereby granted, free of charge, to any person obtaining
49
a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
redistribute, and sell modified and unmodified copies of the Font
51
Software, subject to the following conditions:
52
53
1) Neither the Font Software nor any of its individual components,
54
in Original or Modified Versions, may be sold by itself.
55
56
2) Original or Modified Versions of the Font Software may be bundled,
57
redistributed and/or sold with any software, provided that each copy
58
contains the above copyright notice and this license. These can be
59
included either as stand-alone text files, human-readable headers or
60
in the appropriate machine-readable metadata fields within text or
61
binary files as long as those fields can be easily viewed by the user.
62
63
3) No Modified Version of the Font Software may use the Reserved Font
64
Name(s) unless explicit written permission is granted by the corresponding
65
Copyright Holder. This restriction only applies to the primary font name as
66
presented to the users.
67
68
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
Software shall not be used to promote, endorse or advertise any
70
Modified Version, except to acknowledge the contribution(s) of the
71
Copyright Holder(s) and the Author(s) or with their explicit written
72
permission.
73
74
5) The Font Software, modified or unmodified, in part or in whole,
75
must be distributed entirely under this license, and must not be
76
distributed under any other license. The requirement for fonts to
77
remain under this license does not apply to any document created
78
using the Font Software.
79
80
TERMINATION
81
This license becomes null and void if any of the above conditions are
82
not met.
83
84
DISCLAIMER
85
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
OTHER DEALINGS IN THE FONT SOFTWARE.
941
M src/main/java/com/keenwrite/Constants.java
180180
   * Default preview font name.
181181
   */
182
  public static final String FONT_NAME_PREVIEW_DEFAULT = "Source Serif Pro";
182
  public static final String FONT_NAME_PREVIEW_DEFAULT = "Source Serif 4";
183183
184184
  /**
...
222222
   */
223223
  public static final String ICON_SIZE_DEFAULT = "1.2em";
224
225
  public static final String DIAGRAM_SERVER_NAME = "kroki.io";
224226
225227
  /**
M src/main/java/com/keenwrite/MainPane.java
388388
        final var resource = tab.getContent();
389389
390
        if( !(resource instanceof TextResource) ) {
390
        // The definition panes auto-save, so being specific here prevents
391
        // closing the definitions in the situation where the user wants to
392
        // continue editing (i.e., possibly save unsaved work).
393
        if( !(resource instanceof TextEditor) ) {
391394
          continue;
392395
        }
393396
394
        if( canClose( (TextResource) resource ) ) {
397
        if( canClose( (TextEditor) resource ) ) {
395398
          tabIterator.remove();
396399
          close( tab );
M src/main/java/com/keenwrite/MainScene.java
102102
103103
    internal.addListener(
104
      ( c, o, n ) -> applyStylesheets( scene, inTheme, exTheme )
104
      ( c, o, n ) -> {
105
        if( n != null ) {
106
          applyStylesheets( scene, n, exTheme );
107
        }
108
      }
105109
    );
106110
...
137141
   * @param scene    The scene to stylize.
138142
   * @param internal The CSS file name bundled with the application.
143
   * @param external The (optional) customized CSS file specified by the user.
139144
   */
140145
  private void applyStylesheets(
...
147152
148153
    try {
149
      if( external.canRead() && !external.isDirectory() ) {
154
      if( external != null && external.canRead() && !external.isDirectory() ) {
150155
        stylesheets.add( external.toURI().toURL().toString() );
151
152156
        mFileWatchService.register( external );
153157
      }
M src/main/java/com/keenwrite/Messages.java
3434
  @SuppressWarnings( "SameParameterValue" )
3535
  private static String resolve( final ResourceBundle props, final String s ) {
36
    final int len = s.length();
37
    final Stack<StringBuilder> stack = new Stack<>();
38
39
    StringBuilder sb = new StringBuilder( 256 );
40
    boolean open = false;
36
    final var len = s.length();
37
    final var stack = new Stack<StringBuilder>();
38
    var sb = new StringBuilder( 256 );
39
    var open = false;
4140
42
    for( int i = 0; i < len; i++ ) {
43
      final char c = s.charAt( i );
41
    for( var i = 0; i < len; i++ ) {
42
      final var c = s.charAt( i );
4443
4544
      switch( c ) {
...
6463
          if( open ) {
6564
            open = false;
66
            final String name = sb.toString();
65
            final var name = sb.toString();
6766
6867
            sb = stack.pop();
...
9695
    try {
9796
      return resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) );
98
    } catch( final Exception ex ) {
97
    } catch( final Exception ignored ) {
9998
      return key;
10099
    }
M src/main/java/com/keenwrite/events/StatusEvent.java
1616
 */
1717
public class StatusEvent implements AppEvent {
18
  private static final String PACKAGE_NAME = MainApp.class.getPackageName();
19
1820
  /**
1921
   * Indicates that there are no issues to bring to the user's attention.
...
8082
  private static boolean filter( final StackTraceElement e ) {
8183
    final var clazz = e.getClassName();
82
    return clazz.contains( MainApp.class.getPackageName() ) ||
83
      clazz.startsWith( "org.renjin" );
84
    return clazz.contains( PACKAGE_NAME ) ||
85
      clazz.contains( "org.renjin." ) ||
86
      clazz.contains( "sun." ) ||
87
      clazz.contains( "java." );
8488
  }
8589
8690
  /**
8791
   * Returns the message used to construct the event.
8892
   *
8993
   * @return The message for this event.
9094
   */
91
  public String toString() {
95
  public String getMessage() {
9296
    return mMessage;
9397
  }
...
126130
   */
127131
  public static void clue( final Throwable problem ) {
128
    fireStatusEvent( problem.getMessage() );
132
    fireStatusEvent( problem.getMessage(), problem );
129133
  }
130134
131135
  private static void fireStatusEvent( final String message ) {
132136
    new StatusEvent( message ).fire();
133137
  }
134138
135
  private static void fireStatusEvent( final String message, final Throwable problem ) {
139
  private static void fireStatusEvent(
140
    final String message, final Throwable problem ) {
136141
    new StatusEvent( message, problem ).fire();
137142
  }
M src/main/java/com/keenwrite/io/FileWatchService.java
77
import java.io.File;
88
import java.io.IOException;
9
import java.nio.file.FileSystems;
109
import java.nio.file.Path;
1110
import java.nio.file.WatchKey;
1211
import java.nio.file.WatchService;
1312
import java.util.Set;
1413
import java.util.concurrent.ConcurrentHashMap;
1514
15
import static java.nio.file.FileSystems.getDefault;
1616
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
1717
import static java.util.Collections.newSetFromMap;
...
4040
   */
4141
  public FileWatchService( final File... files ) {
42
    WatchService watchService;
42
    mWatchService = createWatchService();
4343
4444
    try {
45
      watchService = FileSystems.getDefault().newWatchService();
46
4745
      for( final var file : files ) {
4846
        register( file );
4947
      }
50
    } catch( final Exception ignored ) {
51
      // Create a fallback that allows the class to be instantiated and used
52
      // without without preventing the application from launching.
53
      watchService = new PollingWatchService();
48
    } catch( final Exception ex ) {
49
      throw new RuntimeException( ex );
5450
    }
55
56
    mWatchService = watchService;
5751
  }
5852
...
216210
217211
    return directory.toPath();
212
  }
213
214
  private WatchService createWatchService() {
215
    try {
216
      return getDefault().newWatchService();
217
    } catch( final Exception ex ) {
218
      // Create a fallback that allows the class to be instantiated and used
219
      // without without preventing the application from launching.
220
      return new PollingWatchService();
221
    }
218222
  }
219223
}
M src/main/java/com/keenwrite/io/HttpMediaType.java
22
package com.keenwrite.io;
33
4
import javax.net.ssl.*;
45
import java.net.MalformedURLException;
6
import java.net.Socket;
57
import java.net.URI;
68
import java.net.URL;
79
import java.net.http.HttpClient;
810
import java.net.http.HttpRequest;
11
import java.security.cert.X509Certificate;
912
1013
import static com.keenwrite.events.StatusEvent.clue;
1114
import static com.keenwrite.io.MediaType.UNDEFINED;
1215
import static java.net.http.HttpClient.Redirect.NORMAL;
1316
import static java.net.http.HttpRequest.BodyPublishers.noBody;
14
import static java.net.http.HttpResponse.BodyHandlers.discarding;
17
import static java.net.http.HttpResponse.BodyHandlers.ofString;
1518
import static java.time.Duration.ofSeconds;
1619
1720
/**
1821
 * Responsible for determining {@link MediaType} based on the content-type from
1922
 * an HTTP request.
2023
 */
2124
public final class HttpMediaType {
2225
23
  private final static HttpClient HTTP_CLIENT = HttpClient
26
  static {
27
    disableSSLVerification();
28
  }
29
30
  private static final HttpClient HTTP_CLIENT = HttpClient
2431
    .newBuilder()
25
    .connectTimeout( ofSeconds( 5 ) )
32
    .connectTimeout( ofSeconds( 10 ) )
2633
    .followRedirects( NORMAL )
2734
    .build();
...
3542
   * unmapped.
3643
   * @throws MalformedURLException The {@link URI} could not be converted to
37
   *                               a {@link URL}.
44
   *                               an instance of {@link URL}.
3845
   */
3946
  public static MediaType valueFrom( final URI uri )
4047
    throws MalformedURLException {
4148
    final var mediaType = new MediaType[]{UNDEFINED};
4249
4350
    try {
4451
      clue( "Main.status.image.request.init" );
4552
      final var request = HttpRequest
46
        .newBuilder( uri )
53
        .newBuilder()
4754
        .method( "HEAD", noBody() )
55
        .uri( uri )
4856
        .build();
4957
      clue( "Main.status.image.request.fetch", uri.getHost() );
50
      final var response = HTTP_CLIENT.send( request, discarding() );
58
      final var response = HTTP_CLIENT.send( request, ofString() );
5159
      final var headers = response.headers();
5260
      final var map = headers.map();
...
6977
        }
7078
      } );
79
80
      clue();
7181
    } catch( final Exception ex ) {
7282
      clue( ex );
7383
    }
7484
7585
    return mediaType[ 0 ];
86
  }
87
88
  // Method used for bypassing SSL verification
89
  private static void disableSSLVerification() {
90
91
    TrustManager[] trustAllCerts =
92
      new TrustManager[]{new X509ExtendedTrustManager() {
93
        @Override
94
        public void checkClientTrusted( X509Certificate[] chain,
95
                                        String authType,
96
                                        Socket socket ) {
97
98
        }
99
100
        @Override
101
        public void checkServerTrusted( X509Certificate[] chain,
102
                                        String authType,
103
                                        Socket socket ) {
104
105
        }
106
107
        @Override
108
        public void checkClientTrusted( X509Certificate[] chain,
109
                                        String authType,
110
                                        SSLEngine engine ) {
111
112
        }
113
114
        @Override
115
        public void checkServerTrusted( X509Certificate[] chain,
116
                                        String authType,
117
                                        SSLEngine engine ) {
118
119
        }
120
121
        @Override
122
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
123
          return null;
124
        }
125
126
        @Override
127
        public void checkClientTrusted(
128
          X509Certificate[] certs, String authType ) {
129
        }
130
131
        @Override
132
        public void checkServerTrusted(
133
          X509Certificate[] certs, String authType ) {
134
        }
135
      }};
136
137
    try {
138
      final var context = SSLContext.getInstance( "SSL" );
139
      context.init( null, trustAllCerts, new java.security.SecureRandom() );
140
      HttpsURLConnection.setDefaultSSLSocketFactory( context.getSocketFactory() );
141
      HttpsURLConnection.setDefaultHostnameVerifier( ( hostname, session ) -> true );
142
    } catch( final Exception ex ) {
143
      clue( ex );
144
    }
76145
  }
77146
}
M src/main/java/com/keenwrite/preferences/Workspace.java
418418
  private Object unmarshall(
419419
    final Property<?> property, final Object configValue ) {
420
    String setting = configValue.toString();
421
422
    // TODO: #118 - Font upgrade, which can be removed in a few releases.
423
    if( setting.equalsIgnoreCase( "Source Serif Pro" ) ) {
424
      setting = "Source Serif 4";
425
    }
426
420427
    return UNMARSHALL
421428
      .getOrDefault( property.getClass(), ( value ) -> value )
422
      .apply( configValue.toString() );
429
      .apply( setting );
423430
  }
424431
425432
  private Object marshall( final Property<?> property ) {
426
    return MARSHALL
433
    return property.getValue() == null
434
      ? null
435
      : MARSHALL
427436
      .getOrDefault( property.getClass(), ( __ ) -> property.getValue() )
428437
      .apply( property.getValue().toString() );
M src/main/java/com/keenwrite/preview/ChainedReplacedElementFactory.java
1
/* Copyright 2006 Patrick Wright
2
 * Copyright 2007 Wisconsin Court System
3
 * Copyright 2020-2021 White Magic Software, Ltd.
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public License
7
 * as published by the Free Software Foundation; either version 2.1
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
 */
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
192
package com.keenwrite.preview;
203
M src/main/java/com/keenwrite/preview/HtmlPanel.java
2323
import static java.awt.Desktop.Action.BROWSE;
2424
import static java.awt.Desktop.getDesktop;
25
import static java.lang.Boolean.FALSE;
26
import static java.lang.Boolean.TRUE;
2527
import static javax.swing.SwingUtilities.invokeLater;
2628
import static javax.swing.SwingUtilities.isEventDispatchThread;
...
4042
    @Override
4143
    public void documentStarted() {
42
      mReadyProperty.setValue( Boolean.FALSE );
44
      mReadyProperty.setValue( FALSE );
4345
    }
4446
4547
    @Override
4648
    public void documentLoaded() {
47
      mReadyProperty.setValue( Boolean.TRUE );
49
      mReadyProperty.setValue( TRUE );
4850
    }
4951
  }
M src/main/java/com/keenwrite/preview/HtmlPreview.java
22
package com.keenwrite.preview;
33
4
import com.keenwrite.preferences.LocaleProperty;
5
import com.keenwrite.preferences.Workspace;
6
import javafx.application.Platform;
7
import javafx.beans.property.DoubleProperty;
8
import javafx.beans.property.StringProperty;
9
import javafx.embed.swing.SwingNode;
10
import org.xhtmlrenderer.render.Box;
11
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
12
13
import javax.swing.*;
14
import java.awt.*;
15
import java.net.URL;
16
import java.nio.file.Path;
17
import java.util.Locale;
18
19
import static com.keenwrite.Constants.*;
20
import static com.keenwrite.Messages.get;
21
import static com.keenwrite.events.StatusEvent.clue;
22
import static com.keenwrite.preferences.WorkspaceKeys.*;
23
import static java.lang.Math.max;
24
import static java.lang.String.format;
25
import static java.lang.Thread.sleep;
26
import static javafx.application.Platform.runLater;
27
import static javafx.scene.CacheHint.SPEED;
28
import static javax.swing.SwingUtilities.invokeLater;
29
30
/**
31
 * Responsible for parsing an HTML document.
32
 */
33
public final class HtmlPreview extends SwingNode {
34
35
  // The order is important: Swing factory will replace SVG images with
36
  // a blank image, which will cause the chained factory to cache the image
37
  // and exit. Instead, the SVG must execute first to rasterize the content.
38
  // Consequently, the chained factory must maintain insertion order.
39
  private static final ChainedReplacedElementFactory FACTORY
40
    = new ChainedReplacedElementFactory(
41
    new SvgReplacedElementFactory(),
42
    new SwingReplacedElementFactory()
43
  );
44
45
  /**
46
   * Render CSS using points (pt) not pixels (px) to reduce the chance of
47
   * poor rendering. The {@link #head()} method fills out the placeholders.
48
   */
49
  private static final String HTML_HEAD =
50
    """
51
      <!DOCTYPE html>
52
      <html lang='%s'><head><title> </title><meta charset='utf-8'>
53
      <link rel='stylesheet' href='%s'>
54
      <link rel='stylesheet' href='%s'>
55
      <style>body{font-family:'%s';font-size: %spt;}</style>
56
      <base href='%s'>
57
      </head><body>
58
      """;
59
60
  private static final String HTML_TAIL = "</body></html>";
61
62
  private static final URL HTML_STYLE_PREVIEW = toUrl( STYLESHEET_PREVIEW );
63
64
  /**
65
   * The buffer is reused so that previous memory allocations need not repeat.
66
   */
67
  private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
68
69
  private HtmlPanel mView;
70
  private JScrollPane mScrollPane;
71
  private String mBaseUriPath = "";
72
  private URL mLocaleUrl;
73
  private final Workspace mWorkspace;
74
75
  /**
76
   * Creates a new preview pane that can scroll to the caret position within the
77
   * document.
78
   *
79
   * @param workspace Contains locale and font size information.
80
   */
81
  public HtmlPreview( final Workspace workspace ) {
82
    mWorkspace = workspace;
83
    mLocaleUrl = toUrl( getLocale() );
84
85
    // Attempts to prevent a flash of black un-styled content upon load.
86
    setStyle( "-fx-background-color: white;" );
87
88
    invokeLater( () -> {
89
      mView = new HtmlPanel();
90
      mScrollPane = new JScrollPane( mView );
91
92
      // Enabling the cache attempts to prevent black flashes when resizing.
93
      setCache( true );
94
      setCacheHint( SPEED );
95
      setContent( mScrollPane );
96
97
      final var context = mView.getSharedContext();
98
      final var textRenderer = context.getTextRenderer();
99
      context.setReplacedElementFactory( FACTORY );
100
      textRenderer.setSmoothingThreshold( 0 );
101
102
      localeProperty().addListener( ( c, o, n ) -> {
103
        mLocaleUrl = toUrl( getLocale() );
104
        rerender();
105
      } );
106
107
      fontNameProperty().addListener( ( c, o, n ) -> rerender() );
108
      fontSizeProperty().addListener( ( c, o, n ) -> rerender() );
109
    } );
110
  }
111
112
  /**
113
   * Updates the internal HTML source shown in the preview pane.
114
   *
115
   * @param html The new HTML document to display.
116
   */
117
  public void render( final String html ) {
118
    mView.render( decorate( html ), getBaseUri() );
119
  }
120
121
  /**
122
   * Clears the caches then rerenders the content.
123
   */
124
  public void refresh() {
125
    FACTORY.clearCache();
126
    rerender();
127
  }
128
129
  private void rerender() {
130
    render( mHtmlDocument.toString() );
131
  }
132
133
  /**
134
   * Attaches the HTML head prefix and HTML tail suffix to the given HTML
135
   * string.
136
   *
137
   * @param html The HTML to adorn with opening and closing tags.
138
   * @return A complete HTML document, ready for rendering.
139
   */
140
  private String decorate( final String html ) {
141
    mHtmlDocument.setLength( 0 );
142
    mHtmlDocument.append( html );
143
    return head() + mHtmlDocument + tail();
144
  }
145
146
  private String head() {
147
    return format(
148
      HTML_HEAD,
149
      getLocale().getLanguage(),
150
      HTML_STYLE_PREVIEW,
151
      mLocaleUrl,
152
      getFontName(),
153
      getFontSize(),
154
      mBaseUriPath
155
    );
156
  }
157
158
  private String tail() {
159
    return HTML_TAIL;
160
  }
161
162
  /**
163
   * Clears the preview pane by rendering an empty string.
164
   */
165
  public void clear() {
166
    render( "" );
167
  }
168
169
  /**
170
   * Sets the base URI to the containing directory the file being edited.
171
   *
172
   * @param path The path to the file being edited.
173
   */
174
  public void setBaseUri( final Path path ) {
175
    final var parent = path.getParent();
176
    mBaseUriPath = parent == null ? "" : parent.toUri().toString();
177
  }
178
179
  /**
180
   * Scrolls to the closest element matching the given identifier without
181
   * waiting for the document to be ready.
182
   *
183
   * @param id Scroll the preview pane to this unique paragraph identifier.
184
   */
185
  public void scrollTo( final String id ) {
186
    final Runnable scrollToBox = () -> {
187
      int iter = 0;
188
      Box box = null;
189
190
      while( iter++ < 3 && ((box = mView.getBoxById( id )) == null) ) {
191
        try {
192
          sleep( 10 );
193
        } catch( final Exception ex ) {
194
          clue( ex );
195
        }
196
      }
197
198
      scrollTo( box );
199
    };
200
201
    if( Platform.isFxApplicationThread() ) {
202
      scrollToBox.run();
203
    }
204
    else {
205
      runLater( scrollToBox );
206
    }
207
  }
208
209
  /**
210
   * Scrolls to the location specified by the {@link Box} that corresponds
211
   * to a point somewhere in the preview pane. If there is no caret, then
212
   * this will not change the scroll position. Changing the scroll position
213
   * to the top if the {@link Box} instance is {@code null} will result in
214
   * jumping around a lot and inconsistent synchronization issues.
215
   *
216
   * @param box The rectangular region containing the caret, or {@code null}
217
   *            if the HTML does not have a caret.
218
   */
219
  private void scrollTo( final Box box ) {
220
    if( box != null ) {
221
      scrollTo( createPoint( box ) );
222
    }
223
  }
224
225
  private void scrollTo( final Point point ) {
226
    invokeLater( () -> {
227
      mView.scrollTo( point );
228
      getScrollPane().repaint();
229
    } );
230
  }
231
232
  /**
233
   * Creates a {@link Point} to use as a reference for scrolling to the area
234
   * described by the given {@link Box}. The {@link Box} coordinates are used
235
   * to populate the {@link Point}'s location, with minor adjustments for
236
   * vertical centering.
237
   *
238
   * @param box The {@link Box} that represents a scrolling anchor reference.
239
   * @return A coordinate suitable for scrolling to.
240
   */
241
  private Point createPoint( final Box box ) {
242
    assert box != null;
243
244
    // Scroll back up by half the height of the scroll bar to keep the typing
245
    // area within the view port. Otherwise the view port will have jumped too
246
    // high up and the most recently typed letters won't be visible.
247
    int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 );
248
    int x = box.getAbsX();
249
250
    if( !box.getStyle().isInline() ) {
251
      final var margin = box.getMargin( mView.getLayoutContext() );
252
      y += margin.top();
253
      x += margin.left();
254
    }
255
256
    return new Point( x, y );
257
  }
258
259
  private String getBaseUri() {
260
    return mBaseUriPath;
261
  }
262
263
  private JScrollPane getScrollPane() {
264
    return mScrollPane;
265
  }
266
267
  public JScrollBar getVerticalScrollBar() {
268
    return getScrollPane().getVerticalScrollBar();
269
  }
270
271
  private int getVerticalScrollBarHeight() {
272
    return getVerticalScrollBar().getHeight();
273
  }
274
275
  /**
276
   * Returns the ISO 639 alpha-2 or alpha-3 language code followed by a hyphen
277
   * followed by the ISO 15924 alpha-4 script code, followed by an ISO 3166
278
   * alpha-2 country code or UN M.49 numeric-3 area code. For example, this
279
   * could return "en-Latn-CA" for Canadian English written in the Latin
280
   * character set.
281
   *
282
   * @return Unique identifier for language and country.
283
   */
284
  private static URL toUrl( final Locale locale ) {
285
    return toUrl(
286
      get(
287
        sSettings.getSetting( STYLESHEET_PREVIEW_LOCALE, "" ),
288
        locale.getLanguage(),
289
        locale.getScript(),
290
        locale.getCountry()
291
      )
292
    );
293
  }
294
295
  private static URL toUrl( final String path ) {
296
    return HtmlPreview.class.getResource( path );
297
  }
298
299
  private Locale getLocale() {
300
    return localeProperty().toLocale();
301
  }
302
303
  private LocaleProperty localeProperty() {
304
    return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
305
  }
306
307
  private String getFontName() {
308
    return fontNameProperty().get();
309
  }
310
311
  private StringProperty fontNameProperty() {
312
    return mWorkspace.stringProperty( KEY_UI_FONT_PREVIEW_NAME );
313
  }
314
315
  private double getFontSize() {
316
    return fontSizeProperty().get();
317
  }
318
4
import com.keenwrite.Constants;
5
import com.keenwrite.preferences.LocaleProperty;
6
import com.keenwrite.preferences.Workspace;
7
import javafx.application.Platform;
8
import javafx.beans.property.DoubleProperty;
9
import javafx.beans.property.StringProperty;
10
import javafx.embed.swing.SwingNode;
11
import org.xhtmlrenderer.render.Box;
12
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
13
14
import javax.swing.*;
15
import java.awt.*;
16
import java.net.URL;
17
import java.nio.file.Path;
18
import java.util.Locale;
19
20
import static com.keenwrite.Constants.*;
21
import static com.keenwrite.Messages.get;
22
import static com.keenwrite.events.StatusEvent.clue;
23
import static com.keenwrite.preferences.WorkspaceKeys.*;
24
import static java.lang.Math.max;
25
import static java.lang.String.format;
26
import static java.lang.Thread.sleep;
27
import static javafx.application.Platform.runLater;
28
import static javafx.scene.CacheHint.SPEED;
29
import static javax.swing.SwingUtilities.invokeLater;
30
31
/**
32
 * Responsible for parsing an HTML document.
33
 */
34
public final class HtmlPreview extends SwingNode {
35
36
  /**
37
   * The order is important: Swing factory will replace SVG images with
38
   * a blank image, which will cause the chained factory to cache the image
39
   * and exit. Instead, the SVG must execute first to rasterize the content.
40
   * Consequently, the chained factory must maintain insertion order.
41
   */
42
  private static final ChainedReplacedElementFactory FACTORY
43
    = new ChainedReplacedElementFactory(
44
    new SvgReplacedElementFactory(),
45
    new SwingReplacedElementFactory()
46
  );
47
48
  /**
49
   * Used to populate the {@link #HTML_HEAD} with stylesheet file references.
50
   */
51
  private static final String HTML_STYLESHEET =
52
    "<link rel='stylesheet' href='%s'/>";
53
54
  /**
55
   * Render CSS using points (pt) not pixels (px) to reduce the chance of
56
   * poor rendering. The {@link #head()} method fills out the placeholders.
57
   * When the user has not set a locale, only one stylesheet is added to
58
   * the document.
59
   * <p>
60
   * Do not use points, only pixels here.
61
   * </p>
62
   */
63
  private static final String HTML_HEAD =
64
    """
65
      <!doctype html>
66
      <html lang='%s'><head><title> </title><meta charset='utf-8'/>
67
      %s%s<style>body{font-family:'%s';font-size: %dpx;}</style>
68
      <base href='%s'/></head><body>
69
      """;
70
71
  private static final String HTML_TAIL = "</body></html>";
72
73
  private static final URL HTML_STYLE_PREVIEW = toUrl( STYLESHEET_PREVIEW );
74
75
  /**
76
   * The buffer is reused so that previous memory allocations need not repeat.
77
   */
78
  private final StringBuilder mHtmlDocument = new StringBuilder( 65536 );
79
80
  private HtmlPanel mView;
81
  private JScrollPane mScrollPane;
82
  private String mBaseUriPath = "";
83
84
  /**
85
   * Populates {@link Constants#STYLESHEET_PREVIEW_LOCALE} for stylesheet.
86
   */
87
  private URL mLocaleUrl;
88
89
  private final Workspace mWorkspace;
90
91
  /**
92
   * Creates a new preview pane that can scroll to the caret position within the
93
   * document.
94
   *
95
   * @param workspace Contains locale and font size information.
96
   */
97
  public HtmlPreview( final Workspace workspace ) {
98
    mWorkspace = workspace;
99
    mLocaleUrl = toUrl( getLocale() );
100
101
    // Attempts to prevent a flash of black un-styled content upon load.
102
    setStyle( "-fx-background-color: white;" );
103
104
    invokeLater( () -> {
105
      mView = new HtmlPanel();
106
      mScrollPane = new JScrollPane( mView );
107
108
      // Enabling the cache attempts to prevent black flashes when resizing.
109
      setCache( true );
110
      setCacheHint( SPEED );
111
      setContent( mScrollPane );
112
113
      final var context = mView.getSharedContext();
114
      final var textRenderer = context.getTextRenderer();
115
      context.setReplacedElementFactory( FACTORY );
116
      textRenderer.setSmoothingThreshold( 0 );
117
118
      localeProperty().addListener( ( c, o, n ) -> {
119
        mLocaleUrl = toUrl( getLocale() );
120
        rerender();
121
      } );
122
123
      fontFamilyProperty().addListener( ( c, o, n ) -> rerender() );
124
      fontSizeProperty().addListener( ( c, o, n ) -> rerender() );
125
    } );
126
  }
127
128
  /**
129
   * Updates the internal HTML source shown in the preview pane.
130
   *
131
   * @param html The new HTML document to display.
132
   */
133
  public void render( final String html ) {
134
    mView.render( decorate( html ), getBaseUri() );
135
  }
136
137
  /**
138
   * Clears the caches then rerenders the content.
139
   */
140
  public void refresh() {
141
    FACTORY.clearCache();
142
    rerender();
143
  }
144
145
  private void rerender() {
146
    render( mHtmlDocument.toString() );
147
  }
148
149
  /**
150
   * Attaches the HTML head prefix and HTML tail suffix to the given HTML
151
   * string.
152
   *
153
   * @param html The HTML to adorn with opening and closing tags.
154
   * @return A complete HTML document, ready for rendering.
155
   */
156
  private String decorate( final String html ) {
157
    mHtmlDocument.setLength( 0 );
158
    mHtmlDocument.append( head() );
159
    mHtmlDocument.append( html );
160
    mHtmlDocument.append( tail() );
161
    return mHtmlDocument.toString();
162
  }
163
164
  private String head() {
165
    return format(
166
      HTML_HEAD,
167
      getLocale().getLanguage(),
168
      format( HTML_STYLESHEET, HTML_STYLE_PREVIEW ),
169
      mLocaleUrl == null ? "" : format( HTML_STYLESHEET, mLocaleUrl ),
170
      getFontFamily(),
171
      (int) (getFontSize() * (1 + 1 / 3f)),
172
      mBaseUriPath
173
    );
174
  }
175
176
  private String tail() {
177
    return HTML_TAIL;
178
  }
179
180
  /**
181
   * Clears the preview pane by rendering an empty string.
182
   */
183
  public void clear() {
184
    render( "" );
185
  }
186
187
  /**
188
   * Sets the base URI to the containing directory the file being edited.
189
   *
190
   * @param path The path to the file being edited.
191
   */
192
  public void setBaseUri( final Path path ) {
193
    final var parent = path.getParent();
194
    mBaseUriPath = parent == null ? "" : parent.toUri().toString();
195
  }
196
197
  /**
198
   * Scrolls to the closest element matching the given identifier without
199
   * waiting for the document to be ready.
200
   *
201
   * @param id Scroll the preview pane to this unique paragraph identifier.
202
   */
203
  public void scrollTo( final String id ) {
204
    final Runnable scrollToBox = () -> {
205
      int iter = 0;
206
      Box box = null;
207
208
      while( iter++ < 3 && ((box = mView.getBoxById( id )) == null) ) {
209
        try {
210
          sleep( 10 );
211
        } catch( final Exception ex ) {
212
          clue( ex );
213
        }
214
      }
215
216
      scrollTo( box );
217
    };
218
219
    if( Platform.isFxApplicationThread() ) {
220
      scrollToBox.run();
221
    }
222
    else {
223
      runLater( scrollToBox );
224
    }
225
  }
226
227
  /**
228
   * Scrolls to the location specified by the {@link Box} that corresponds
229
   * to a point somewhere in the preview pane. If there is no caret, then
230
   * this will not change the scroll position. Changing the scroll position
231
   * to the top if the {@link Box} instance is {@code null} will result in
232
   * jumping around a lot and inconsistent synchronization issues.
233
   *
234
   * @param box The rectangular region containing the caret, or {@code null}
235
   *            if the HTML does not have a caret.
236
   */
237
  private void scrollTo( final Box box ) {
238
    if( box != null ) {
239
      scrollTo( createPoint( box ) );
240
    }
241
  }
242
243
  private void scrollTo( final Point point ) {
244
    invokeLater( () -> {
245
      mView.scrollTo( point );
246
      getScrollPane().repaint();
247
    } );
248
  }
249
250
  /**
251
   * Creates a {@link Point} to use as a reference for scrolling to the area
252
   * described by the given {@link Box}. The {@link Box} coordinates are used
253
   * to populate the {@link Point}'s location, with minor adjustments for
254
   * vertical centering.
255
   *
256
   * @param box The {@link Box} that represents a scrolling anchor reference.
257
   * @return A coordinate suitable for scrolling to.
258
   */
259
  private Point createPoint( final Box box ) {
260
    assert box != null;
261
262
    // Scroll back up by half the height of the scroll bar to keep the typing
263
    // area within the view port. Otherwise the view port will have jumped too
264
    // high up and the most recently typed letters won't be visible.
265
    int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 );
266
    int x = box.getAbsX();
267
268
    if( !box.getStyle().isInline() ) {
269
      final var margin = box.getMargin( mView.getLayoutContext() );
270
      y += margin.top();
271
      x += margin.left();
272
    }
273
274
    return new Point( x, y );
275
  }
276
277
  private String getBaseUri() {
278
    return mBaseUriPath;
279
  }
280
281
  private JScrollPane getScrollPane() {
282
    return mScrollPane;
283
  }
284
285
  public JScrollBar getVerticalScrollBar() {
286
    return getScrollPane().getVerticalScrollBar();
287
  }
288
289
  private int getVerticalScrollBarHeight() {
290
    return getVerticalScrollBar().getHeight();
291
  }
292
293
  /**
294
   * Returns the ISO 639 alpha-2 or alpha-3 language code followed by a hyphen
295
   * followed by the ISO 15924 alpha-4 script code, followed by an ISO 3166
296
   * alpha-2 country code or UN M.49 numeric-3 area code. For example, this
297
   * could return "en-Latn-CA" for Canadian English written in the Latin
298
   * character set.
299
   *
300
   * @return Unique identifier for language and country.
301
   */
302
  private static URL toUrl( final Locale locale ) {
303
    return toUrl(
304
      get(
305
        sSettings.getSetting( STYLESHEET_PREVIEW_LOCALE, "" ),
306
        locale.getLanguage(),
307
        locale.getScript(),
308
        locale.getCountry()
309
      )
310
    );
311
  }
312
313
  private static URL toUrl( final String path ) {
314
    return HtmlPreview.class.getResource( path );
315
  }
316
317
  private Locale getLocale() {
318
    return localeProperty().toLocale();
319
  }
320
321
  private LocaleProperty localeProperty() {
322
    return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
323
  }
324
325
  private String getFontFamily() {
326
    return fontFamilyProperty().get();
327
  }
328
329
  private StringProperty fontFamilyProperty() {
330
    return mWorkspace.stringProperty( KEY_UI_FONT_PREVIEW_NAME );
331
  }
332
333
  private double getFontSize() {
334
    return fontSizeProperty().get();
335
  }
336
337
  /**
338
   * Returns the font size in points.
339
   * @return The user-defined font size (in pt).
340
   */
319341
  private DoubleProperty fontSizeProperty() {
320342
    return mWorkspace.doubleProperty( KEY_UI_FONT_PREVIEW_SIZE );
M src/main/java/com/keenwrite/processors/markdown/extensions/FencedBlockExtension.java
1818
import java.util.zip.Deflater;
1919
20
import static com.keenwrite.Constants.DIAGRAM_SERVER_NAME;
2021
import static com.keenwrite.events.StatusEvent.clue;
2122
import static com.keenwrite.processors.IdentityProcessor.IDENTITY;
...
9798
          final var encoded = encode( text );
9899
          final var source = format(
99
            "https://kroki.io/%s/svg/%s", type, encoded );
100
            "https://%s/%s/svg/%s", DIAGRAM_SERVER_NAME, type, encoded );
100101
101102
          final var link = context.resolveLink( LINK, source, false );
M src/main/java/com/keenwrite/ui/controls/EventedStatusBar.java
2727
  @Subscribe
2828
  public void handle( final StatusEvent event ) {
29
    final var message = event.toString();
29
    final var message = event.getMessage();
3030
3131
    // Don't burden the repaint thread if there's no status bar change.
M src/main/java/com/keenwrite/ui/logging/LogView.java
1616
import java.util.TreeSet;
1717
18
import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE;
1819
import static com.keenwrite.Constants.ICON_DIALOG;
1920
import static com.keenwrite.Messages.get;
2021
import static com.keenwrite.events.Bus.register;
2122
import static com.keenwrite.events.StatusEvent.clue;
23
import static java.nio.file.Files.createTempFile;
24
import static java.nio.file.Files.write;
25
import static java.nio.file.StandardOpenOption.APPEND;
26
import static java.nio.file.StandardOpenOption.CREATE;
2227
import static java.time.LocalDateTime.now;
2328
import static java.time.format.DateTimeFormatter.ofPattern;
29
import static javafx.application.Platform.runLater;
2430
import static javafx.collections.FXCollections.observableArrayList;
2531
import static javafx.event.ActionEvent.ACTION;
...
6066
  @Subscribe
6167
  public void log( final StatusEvent event ) {
62
    final var logEntry = new LogEntry( event );
68
    runLater( () ->{
69
      final var logEntry = new LogEntry( event );
6370
64
    if( !mEntries.contains( logEntry ) ) {
65
      mEntries.add( logEntry );
71
      if( !mEntries.contains( logEntry ) ) {
72
        mEntries.add( logEntry );
6673
67
      while( mEntries.size() > CACHE_SIZE ) {
68
        mEntries.remove( 0 );
69
      }
74
        while( mEntries.size() > CACHE_SIZE ) {
75
          mEntries.remove( 0 );
76
        }
7077
71
      mTable.scrollTo( logEntry );
72
    }
78
        mTable.scrollTo( logEntry );
79
      }
80
    });
7381
  }
7482
...
164172
    public LogEntry( final StatusEvent event ) {
165173
      mDate = new SimpleStringProperty( toString( now() ) );
166
      mMessage = new SimpleStringProperty( event.toString() );
174
      mMessage = new SimpleStringProperty( event.getMessage() );
167175
      mTrace = new SimpleStringProperty( event.getProblem() );
168176
    }
...
180188
    }
181189
182
    private String toString( final LocalDateTime date ) {
183
      return date.format( ofPattern( "d MMM u HH:mm:ss" ) );
190
    /**
191
     * Call from constructor to save log message for debugging purposes.
192
     */
193
    @SuppressWarnings( "unused" )
194
    private void persist() {
195
      try {
196
        final var file = createTempFile( APP_TITLE_LOWERCASE, ".log" );
197
        write( file, toString().getBytes(), CREATE, APPEND );
198
      } catch( final Exception ignored ) {
199
        System.out.println( toString() );
200
      }
184201
    }
185202
...
195212
    public int hashCode() {
196213
      return mMessage != null ? mMessage.hashCode() : 0;
214
    }
215
216
    @Override
217
    public String toString() {
218
      final var date = mDate == null ? "" : mDate.get();
219
      final var message = mMessage == null ? "" : mMessage.get();
220
      final var trace = mTrace == null ? "" : mTrace.get();
221
222
      return "LogEntry{" +
223
        "mDate=" + (date == null ? "''" : date) +
224
        ", mMessage=" + (message == null ? "''" : message) +
225
        ", mTrace=" + (trace == null ? "''" : trace) +
226
        '}';
227
    }
228
229
    private String toString( final LocalDateTime date ) {
230
      return date.format( ofPattern( "d MMM u HH:mm:ss" ) );
197231
    }
198232
  }
M src/main/resources/com/keenwrite/messages.properties
5050
Main.status.image.request.init=Initializing HTTP request
5151
Main.status.image.request.fetch=Requesting content type from {0}
52
Main.status.image.request.success=Detected content type ''{0}''
52
Main.status.image.request.success=Determined content type ''{0}''
53
Main.status.image.request.error.media=No media type for ''{0}''
54
Main.status.image.request.error.cert=Could not accept certificate for ''{0}''
5355
5456
Main.status.font.search.missing=No font name starting with ''{0}'' was found
M src/main/resources/com/keenwrite/preview/webview.css
1
html{box-sizing:border-box;font-size:12pt}body,h1,h2,h3,h4,h5,h6,ol,p,ul{margin:0;padding:0}img{max-width:100%;height:auto}table{table-collapse:collapse;table-spacing:0;border-spacing:0}
1
body,h1,h2,h3,h4,h5,h6,ol,p,ul{margin:0;padding:0}img{max-width:100%;height:auto}table{table-collapse:collapse;table-spacing:0;border-spacing:0}
22
3
/* Do not use points (pt): FlyingSaucer on Debian fails to render. */
34
body {
4
  /* Noto Serif introduces whitespace on style transitions. */
5
  font-family: 'Source Serif Pro';
6
  font-size: 12pt;
7
85
  background-color: #fff;
96
  margin: 0 auto;
107
  line-height: 1.6;
118
  color: #454545;
12
  padding: 1em;
9
  padding: .5em;
1310
  font-feature-settings: 'liga' 1;
1411
  font-variant-ligatures: normal;
...
4239
4340
h1 {
44
  font-size: 21pt;
41
  font-size: 28px;
4542
}
4643
4744
h2 {
48
  font-size: 18pt;
45
  font-size: 24px;
4946
  border-bottom: 1px solid #ccc;
5047
}
5148
5249
h3 {
53
  font-size: 15pt;
50
  font-size: 20px;
5451
}
5552
5653
h4 {
57
  font-size: 13.5pt;
54
  font-size: 18px;
5855
}
5956
6057
h5 {
61
  font-size: 12pt;
58
  font-size: 16px;
6259
}
6360
6461
h6 {
65
  font-size: 10.5pt;
62
  font-size: 14px;
6663
}
6764
D src/main/resources/fonts/font-names
1
#!/usr/bin/env bash
2
3
# Writes the name for all OTF files found in the current directory or lower
4
5
find . -type f \( -name "*otf" -o -name "*ttf" \) -exec \
6
  fc-scan --format "%{foundry}: %{family}\n" {} \; | uniq | sort
7
81
A src/main/resources/fonts/source-serif-4/SourceSerif4-Bold.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-BoldItalic.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-Italic.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-Regular.otf
Binary file
D src/main/resources/fonts/source-serif-pro/SourceSerifPro-Bold.otf
Binary file
D src/main/resources/fonts/source-serif-pro/SourceSerifPro-BoldItalic.otf
Binary file
D src/main/resources/fonts/source-serif-pro/SourceSerifPro-Italic.otf
Binary file
D src/main/resources/fonts/source-serif-pro/SourceSerifPro-Regular.otf
Binary file