| 59 | 59 | implementation 'org.jsoup:jsoup:1.13.1' |
| 60 | 60 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20' |
| 61 | implementation 'org.xhtmlrenderer:flying-saucer-swt:9.1.20' | |
| 61 | 62 | |
| 62 | 63 | // R |
| ... | ||
| 78 | 79 | implementation 'org.apache.xmlgraphics:batik-util:1.13' |
| 79 | 80 | implementation 'org.apache.xmlgraphics:batik-xml:1.13' |
| 80 | ||
| 81 | implementation 'org.apache.bsf:bsf-api:3.1' | |
| 82 | 81 | |
| 83 | 82 | // Misc. |
| ... | ||
| 90 | 89 | |
| 91 | 90 | // Create cross-platform überjar. |
| 92 | // | |
| 93 | 91 | // Including these runtime dependencies breaks creating cross-platform binaries. |
| 94 | 92 | fx.each { fxitem -> |
| 38 | 38 | import java.nio.file.Paths; |
| 39 | 39 | |
| 40 | import static com.scrivenvar.preview.SVGRasterizer.PLACEHOLDER_IMAGE; | |
| 40 | import static com.scrivenvar.preview.SVGRasterizer.BROKEN_IMAGE_PLACEHOLDER; | |
| 41 | 41 | import static org.xhtmlrenderer.swing.AWTFSImage.createImage; |
| 42 | 42 | |
| ... | ||
| 50 | 50 | */ |
| 51 | 51 | private static final FSImage FS_PLACEHOLDER_IMAGE = |
| 52 | createImage( PLACEHOLDER_IMAGE ); | |
| 52 | createImage( BROKEN_IMAGE_PLACEHOLDER ); | |
| 53 | 53 | |
| 54 | 54 | private final IntegerProperty mMaxWidthProperty = new SimpleIntegerProperty(); |
| 44 | 44 | import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; |
| 45 | 45 | import org.xhtmlrenderer.swing.SwingReplacedElementFactory; |
| 46 | import org.xhtmlrenderer.util.Configuration; | |
| 46 | 47 | |
| 47 | 48 | import javax.swing.*; |
| ... | ||
| 140 | 141 | |
| 141 | 142 | mSwingNode.setContent( mScrollPane ); |
| 143 | mSwingNode.setCache( true ); | |
| 142 | 144 | |
| 143 | 145 | mHtmlRenderer.addDocumentListener( mDocumentHandler ); |
| 36 | 36 | import org.apache.batik.transcoder.TranscoderOutput; |
| 37 | 37 | import org.apache.batik.transcoder.image.ImageTranscoder; |
| 38 | import org.w3c.dom.svg.SVGDocument; | |
| 38 | import org.apache.batik.util.XMLResourceDescriptor; | |
| 39 | import org.w3c.dom.Document; | |
| 39 | 40 | |
| 40 | 41 | import java.awt.*; |
| 41 | 42 | import java.awt.image.BufferedImage; |
| 42 | 43 | import java.io.IOException; |
| 44 | import java.io.StringReader; | |
| 43 | 45 | import java.net.URL; |
| 44 | 46 | import java.util.Map; |
| 45 | 47 | |
| 46 | import static java.awt.Color.RED; | |
| 47 | 48 | import static java.awt.Color.WHITE; |
| 48 | 49 | import static java.awt.RenderingHints.*; |
| ... | ||
| 79 | 80 | ); |
| 80 | 81 | |
| 81 | public final static BufferedImage PLACEHOLDER_IMAGE; | |
| 82 | public final static Image BROKEN_IMAGE_PLACEHOLDER; | |
| 82 | 83 | |
| 83 | 84 | static { |
| 85 | // A FontAwesome camera icon, cleft asunder. | |
| 86 | // @formatter:off | |
| 87 | final String BROKEN_IMAGE_SVG = "<svg xmlns='http://www.w3.org/2000/svg' " + | |
| 88 | "viewBox='0 0 150 150' width='150pt' height='150pt' " + | |
| 89 | "fill='#454545'><path d='m41.738281 87.738281c2.480469 11.433594 12" + | |
| 90 | ".464844 19.8125 24.15625 20.269531zm0 0'/><path d='m32.328125 76" + | |
| 91 | ".152344-2.25 1.800781 1.824219 1.527344c.089844-1.125.230468-2.230469" + | |
| 92 | ".425781-3.328125zm0 0'/><path d='m60.953125 7.5-18.90625 1.65625c-5" + | |
| 93 | ".835937.511719-10.746094 4.585938-12.308594 10.222656l-2.75 9" + | |
| 94 | ".917969-25.683593 2.246094c-7.734376.675781-13.460938 7.5-12.785157 15" + | |
| 95 | ".234375l7.355469 84.054687c.675781 7.734375 7.5 13.457031 15.234375 12" + | |
| 96 | ".78125l88.042969-7.703125-23.417969-19.648437c-1.867187.480469-3" + | |
| 97 | ".777344.855469-5.757813 1.027343-19.324218 1.691407-36.398437-12" + | |
| 98 | ".636718-38.089843-31.957031-.171875-1.980469-.136719-3.929687.015625-5" + | |
| 99 | ".851562l-1.824219-1.527344 2.25-1.800781c2.703125-15.277344 15" + | |
| 100 | ".339844-27.492188 31.519531-28.90625 1.570313-.140625 3.121094-.132813" + | |
| 101 | " 4.65625-.066406l7-5.605469zm0 0'/><path d='m58.210938 87.628906 22" + | |
| 102 | ".296874 22.300782c1.070313.132812 2.148438.226562 3.253907.226562 14" + | |
| 103 | ".210937 0 25.78125-11.570312 25.78125-25.78125 0-14.207031-11" + | |
| 104 | ".570313-25.78125-25.78125-25.78125-14.207031 0-25.78125 11.574219-25" + | |
| 105 | ".78125 25.78125 0 1.105469.09375 2.1875.230469 3.253906zm0 0'/><path " + | |
| 106 | "d='m90.203125 6.382812 8.410156 36.082032-7.867187 4.304687c16.9375 3" + | |
| 107 | ".773438 28.917968 19.589844 27.363281 37.339844-1.515625 17.339844-15" + | |
| 108 | ".433594 30.621094-32.238281 31.945313l19.652344 23.421874 33.367187 2" + | |
| 109 | ".917969c7.734375.675781 14.558594-5.050781 15.234375-12.785156l7" + | |
| 110 | ".351562-84.050781c.679688-7.734375-5.046874-14.558594-12.78125-15" + | |
| 111 | ".234375l-25.683593-2.246094-2.78125-9.921875c-1.5625-5.632812-6" + | |
| 112 | ".472657-9.710938-12.308594-10.222656zm0 0'/></svg>"; | |
| 113 | // @formatter:on | |
| 114 | ||
| 115 | // The width and height cannot be embedded in the SVG above because the | |
| 116 | // path element values are relative to the viewBox dimensions. | |
| 84 | 117 | final int w = 150; |
| 85 | 118 | final int h = 150; |
| 86 | final var image = new BufferedImage( w, h, TYPE_INT_RGB ); | |
| 87 | final var graphics = (Graphics2D) image.getGraphics(); | |
| 88 | graphics.setRenderingHints( RENDERING_HINTS ); | |
| 119 | Image image; | |
| 89 | 120 | |
| 90 | graphics.setColor( new Color( 204, 204, 204 ) ); | |
| 91 | graphics.fillRect( 0, 0, w, h ); | |
| 92 | graphics.setColor( new Color( 255, 204, 204 ) ); | |
| 93 | graphics.setStroke( new BasicStroke( 4 ) ); | |
| 94 | graphics.drawOval( w / 4, h / 4, w / 2, h / 2 ); | |
| 95 | graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI), | |
| 96 | h / 4 + (int) (w / 4 / Math.PI), | |
| 97 | w / 2 + w / 4 - (int) (w / 4 / Math.PI), | |
| 98 | h / 2 + h / 4 - (int) (w / 4 / Math.PI) ); | |
| 99 | PLACEHOLDER_IMAGE = image; | |
| 121 | try( final StringReader reader = new StringReader( BROKEN_IMAGE_SVG ) ) { | |
| 122 | final String parser = XMLResourceDescriptor.getXMLParserClassName(); | |
| 123 | final SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory( parser ); | |
| 124 | final Document document = factory.createDocument( "", reader ); | |
| 125 | ||
| 126 | image = rasterize( document, w ); | |
| 127 | } catch( final Exception e ) { | |
| 128 | image = new BufferedImage( w, h, TYPE_INT_RGB ); | |
| 129 | final var graphics = (Graphics2D) image.getGraphics(); | |
| 130 | graphics.setRenderingHints( RENDERING_HINTS ); | |
| 131 | ||
| 132 | // Fall back to a (\) symbol. | |
| 133 | graphics.setColor( new Color( 204, 204, 204 ) ); | |
| 134 | graphics.fillRect( 0, 0, w, h ); | |
| 135 | graphics.setColor( new Color( 255, 204, 204 ) ); | |
| 136 | graphics.setStroke( new BasicStroke( 4 ) ); | |
| 137 | graphics.drawOval( w / 4, h / 4, w / 2, h / 2 ); | |
| 138 | graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI), | |
| 139 | h / 4 + (int) (w / 4 / Math.PI), | |
| 140 | w / 2 + w / 4 - (int) (w / 4 / Math.PI), | |
| 141 | h / 2 + h / 4 - (int) (w / 4 / Math.PI) ); | |
| 142 | } | |
| 143 | ||
| 144 | BROKEN_IMAGE_PLACEHOLDER = image; | |
| 100 | 145 | } |
| 101 | 146 | |
| ... | ||
| 145 | 190 | } catch( final Exception e ) { |
| 146 | 191 | NOTIFIER.notify( e ); |
| 147 | return PLACEHOLDER_IMAGE; | |
| 192 | return BROKEN_IMAGE_PLACEHOLDER; | |
| 148 | 193 | } |
| 149 | 194 | } |
| ... | ||
| 164 | 209 | throws IOException, TranscoderException { |
| 165 | 210 | return rasterize( |
| 166 | (SVGDocument) mFactory.createDocument( url.toString() ), width ); | |
| 211 | mFactory.createDocument( url.toString() ), width ); | |
| 167 | 212 | } |
| 168 | 213 | |
| 169 | 214 | public static Image rasterize( |
| 170 | final SVGDocument svg, final int width ) throws TranscoderException { | |
| 215 | final Document svg, final int width ) throws TranscoderException { | |
| 171 | 216 | final var transcoder = new BufferedImageTranscoder(); |
| 172 | 217 | final var input = new TranscoderInput( svg ); |
| 1 | /* RESET | |
| 2 | =============================================================================*/ | |
| 3 | html, body, div, span, applet, object, iframe, | |
| 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | |
| 5 | a, abbr, acronym, address, big, cite, code, | |
| 6 | del, dfn, em, img, ins, kbd, q, s, samp, | |
| 7 | small, strike, strong, sub, sup, tt, var, | |
| 8 | b, u, i, center, | |
| 9 | dl, dt, dd, ol, ul, li, | |
| 10 | fieldset, form, label, legend, | |
| 11 | table, caption, tbody, tfoot, thead, tr, th, td, | |
| 12 | article, aside, canvas, details, embed, | |
| 13 | figure, figcaption, footer, header, hgroup, | |
| 14 | menu, nav, output, ruby, section, summary, | |
| 15 | time, mark, audio, video { | |
| 16 | margin: 0; | |
| 17 | padding: 0; | |
| 18 | border: 0; | |
| 19 | font-size: 100%; | |
| 20 | font: inherit; | |
| 21 | vertical-align: baseline; | |
| 22 | } | |
| 23 | ol, ul { | |
| 24 | list-style: none; | |
| 25 | } | |
| 26 | blockquote, q { | |
| 27 | quotes: none; | |
| 28 | } | |
| 29 | blockquote:before, blockquote:after, | |
| 30 | q:before, q:after { | |
| 31 | content: ""; | |
| 32 | content: none; | |
| 33 | } | |
| 34 | table { | |
| 35 | border-collapse: collapse; | |
| 36 | border-spacing: 0; | |
| 37 | } | |
| 1 | /* RESET ***/ | |
| 2 | 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} | |
| 38 | 3 | |
| 39 | /* BODY | |
| 40 | =============================================================================*/ | |
| 4 | /* BODY ***/ | |
| 41 | 5 | body { |
| 42 | 6 | /* Must be bundled in JAR file. */ |
| 43 | 7 | font-family: "Vollkorn", serif; |
| 44 | font-size: 16px; | |
| 8 | font-size: 12pt; | |
| 45 | 9 | background-color: #fff; |
| 46 | 10 | margin: 0 auto; |
| 47 | 11 | max-width: 960px; |
| 48 | 12 | line-height: 1.6; |
| 49 | 13 | color: #454545; |
| 50 | padding: 0 1em | |
| 14 | padding: 0 1em; | |
| 51 | 15 | } |
| 52 | 16 | |
| ... | ||
| 59 | 23 | } |
| 60 | 24 | |
| 61 | /* BLOCKS | |
| 62 | =============================================================================*/ | |
| 25 | /* BLOCKS ***/ | |
| 63 | 26 | p, blockquote, ul, ol, dl, table, pre { |
| 64 | 27 | margin: 1em 0; |
| 65 | 28 | } |
| 66 | 29 | |
| 67 | /* HEADERS | |
| 68 | =============================================================================*/ | |
| 30 | /* HEADERS ***/ | |
| 69 | 31 | h1, h2, h3, h4, h5, h6 { |
| 70 | 32 | font-weight: bold; |
| ... | ||
| 78 | 40 | |
| 79 | 41 | h1 { |
| 80 | font-size: 28px; | |
| 42 | font-size: 21pt; | |
| 81 | 43 | } |
| 82 | 44 | |
| 83 | 45 | h2 { |
| 84 | font-size: 24px; | |
| 46 | font-size: 18pt; | |
| 85 | 47 | border-bottom: 1px solid #ccc; |
| 86 | 48 | } |
| 87 | 49 | |
| 88 | 50 | h3 { |
| 89 | font-size: 20px; | |
| 51 | font-size: 15pt; | |
| 90 | 52 | } |
| 91 | 53 | |
| 92 | 54 | h4 { |
| 93 | font-size: 18px; | |
| 55 | font-size: 13.5pt; | |
| 94 | 56 | } |
| 95 | 57 | |
| 96 | 58 | h5 { |
| 97 | font-size: 16px; | |
| 59 | font-size: 12pt; | |
| 98 | 60 | } |
| 99 | 61 | |
| 100 | 62 | h6 { |
| 101 | font-size: 14px; | |
| 63 | font-size: 10.5pt; | |
| 102 | 64 | } |
| 103 | 65 | |
| 104 | 66 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { |
| 105 | 67 | margin-top: .5em; |
| 106 | 68 | } |
| 107 | 69 | |
| 108 | /* LINKS | |
| 109 | =============================================================================*/ | |
| 70 | /* LINKS ***/ | |
| 110 | 71 | a { |
| 111 | 72 | color: #0077aa; |
| 112 | 73 | text-decoration: none; |
| 113 | 74 | } |
| 114 | 75 | |
| 115 | 76 | a:hover { |
| 116 | 77 | text-decoration: underline; |
| 117 | 78 | } |
| 118 | 79 | |
| 119 | /* BULLET LISTS | |
| 120 | =============================================================================*/ | |
| 80 | /* BULLET LISTS ***/ | |
| 121 | 81 | ul, ol { |
| 122 | 82 | display: block; |
| 123 | 83 | list-style: disc outside none; |
| 124 | 84 | margin: 1em 0; |
| 125 | 85 | padding: 0 0 0 2em; |
| 126 | 86 | } |
| 127 | 87 | |
| 128 | 88 | ol { |
| 129 | list-style-type: decimal; | |
| 89 | list-style-type: decimal; | |
| 130 | 90 | } |
| 131 | 91 | |
| 132 | 92 | ul ul, ol ul, |
| 133 | 93 | ol ol, ul ol { |
| 134 | list-style-position: inside; | |
| 135 | margin-left: 1em; | |
| 94 | list-style-position: inside; | |
| 95 | margin-left: 1em; | |
| 136 | 96 | } |
| 137 | 97 | |
| 138 | 98 | ul ul, ol ul { |
| 139 | list-style-type: circle; | |
| 99 | list-style-type: circle; | |
| 140 | 100 | } |
| 141 | 101 | |
| 142 | 102 | ol ol, ul ol { |
| 143 | list-style-type: lower-latin; | |
| 103 | list-style-type: lower-latin; | |
| 144 | 104 | } |
| 145 | 105 | |
| 146 | /* DEFINITION LISTS | |
| 147 | =============================================================================*/ | |
| 106 | /* DEFINITION LISTS ***/ | |
| 148 | 107 | dl { |
| 149 | 108 | /** Horizontal scroll bar will appear if set to 100%. */ |
| ... | ||
| 168 | 127 | } |
| 169 | 128 | |
| 170 | /* CODE | |
| 171 | =============================================================================*/ | |
| 129 | /* CODE ***/ | |
| 172 | 130 | pre, code, tt { |
| 173 | 131 | /* Must be bundled in JAR file. */ |
| 174 | 132 | font-family: "Fira Code", monospace; |
| 175 | font-size: 14px; | |
| 133 | font-size: 14pt; | |
| 176 | 134 | white-space: pre-wrap; |
| 177 | 135 | word-wrap: break-word; |
| ... | ||
| 215 | 173 | } |
| 216 | 174 | |
| 217 | /* QUOTES | |
| 218 | =============================================================================*/ | |
| 175 | /* QUOTES ***/ | |
| 219 | 176 | blockquote { |
| 220 | 177 | border-left: .25em solid #ccc; |
| 221 | 178 | padding: 0 1em; |
| 222 | 179 | color: #777; |
| 223 | 180 | } |
| 224 | 181 | |
| 225 | 182 | blockquote>:first-child { |
| 226 | margin-top: 0px; | |
| 183 | margin-top: 0; | |
| 227 | 184 | } |
| 228 | 185 | |
| 229 | 186 | blockquote>:last-child { |
| 230 | margin-bottom: 0px; | |
| 187 | margin-bottom: 0; | |
| 231 | 188 | } |
| 232 | 189 | |
| 233 | /* HORIZONTAL RULES | |
| 234 | =============================================================================*/ | |
| 190 | /* HORIZONTAL RULES ***/ | |
| 235 | 191 | hr { |
| 236 | 192 | clear: both; |
| 237 | 193 | margin: 1.5em 0 1.5em; |
| 238 | height: 0px; | |
| 194 | height: 0; | |
| 239 | 195 | overflow: hidden; |
| 240 | 196 | border: none; |
| 241 | 197 | background: transparent; |
| 242 | 198 | border-bottom: .125em solid #ccc; |
| 243 | 199 | } |
| 244 | 200 | |
| 245 | /* TABLES | |
| 246 | =============================================================================*/ | |
| 201 | /* TABLES ***/ | |
| 247 | 202 | table { |
| 248 | 203 | width: 100%; |
| ... | ||
| 263 | 218 | } |
| 264 | 219 | |
| 265 | /* IMAGES | |
| 266 | =============================================================================*/ | |
| 220 | /* IMAGES ***/ | |
| 267 | 221 | img { |
| 268 | 222 | max-width: 100%; |