| Author | DaveJarvis <email> |
|---|---|
| Date | 2021-03-21 23:57:50 GMT-0700 |
| Commit | cd8d7bc5c796e49edef5b345f7716c60db79203d |
| Parent | c728521 |
| /** | ||
| + * Exports as PDF file format. | ||
| + */ | ||
| + APPLICATION_PDF( ".pdf" ), | ||
| + | ||
| + /** | ||
| * Indicates no special export format is to be created. No extension is | ||
| * applicable. Image links must use absolute directories. |
| public ProcessorContext createProcessorContext() { | ||
| - return createProcessorContext( NONE ); | ||
| + return createProcessorContext( null, NONE ); | ||
| } | ||
| - public ProcessorContext createProcessorContext( final ExportFormat format ) { | ||
| + public ProcessorContext createProcessorContext( | ||
| + final File exportPath, final ExportFormat format ) { | ||
| final var editor = getActiveTextEditor(); | ||
| return createProcessorContext( | ||
| - editor.getPath(), editor.getCaret(), format ); | ||
| + editor.getPath(), editor.getCaret(), exportPath, format ); | ||
| + } | ||
| + | ||
| + private ProcessorContext createProcessorContext( | ||
| + final Path path, final Caret caret ) { | ||
| + return createProcessorContext( path, caret, null, ExportFormat.NONE ); | ||
| } | ||
| /** | ||
| - * @param path Used by {@link ProcessorFactory} to determine | ||
| - * {@link Processor} type to create based on file type. | ||
| - * @param caret Used by {@link CaretExtension} to add ID attribute into | ||
| - * preview document for scrollbar synchronization. | ||
| + * @param path Used by {@link ProcessorFactory} to determine | ||
| + * {@link Processor} type to create based on file type. | ||
| + * @param caret Used by {@link CaretExtension} to add ID attribute into | ||
| + * preview document for scrollbar synchronization. | ||
| + * @param exportPath Used when exporting to a PDF file (binary). | ||
| + * @param format Used when processors export to a new text format. | ||
| * @return A new {@link ProcessorContext} to use when creating an instance of | ||
| * {@link Processor}. | ||
| */ | ||
| private ProcessorContext createProcessorContext( | ||
| - final Path path, final Caret caret, final ExportFormat format ) { | ||
| + final Path path, final Caret caret, | ||
| + final File exportPath, final ExportFormat format ) { | ||
| return new ProcessorContext( | ||
| - mPreview, mResolvedMap, path, caret, format, mWorkspace | ||
| + mPreview, mResolvedMap, path, caret, exportPath, format, mWorkspace | ||
| ); | ||
| } | ||
| final var editor = new MarkdownEditor( file, getWorkspace() ); | ||
| final var caret = editor.getCaret(); | ||
| - final var context = createProcessorContext( path, caret, NONE ); | ||
| + final var context = createProcessorContext( path, caret ); | ||
| mProcessors.computeIfAbsent( editor, p -> createProcessors( context ) ); | ||
| +/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| +package com.keenwrite.processors; | ||
| + | ||
| +import java.io.File; | ||
| +import java.nio.file.Paths; | ||
| +import java.util.stream.Stream; | ||
| + | ||
| +import static java.io.File.pathSeparator; | ||
| +import static java.lang.System.getenv; | ||
| +import static java.util.regex.Pattern.quote; | ||
| + | ||
| +/** | ||
| + * Responsible for using a typesetting engine to convert an XHTML document | ||
| + * into a PDF file. | ||
| + */ | ||
| +public final class PdfProcessor extends ExecutorProcessor<String> { | ||
| + private static final String TYPESETTER = "context"; | ||
| + | ||
| + private final File mExportPath; | ||
| + | ||
| + public PdfProcessor( final File exportPath ) { | ||
| + assert exportPath != null; | ||
| + mExportPath = exportPath; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Converts a document by calling a third-party library to typeset the given | ||
| + * XHTML document. | ||
| + * | ||
| + * @param xhtml The document to convert to a PDF file. | ||
| + * @return {@code null} because there is no valid return value from generating | ||
| + * a PDF file. | ||
| + */ | ||
| + public String apply( final String xhtml ) { | ||
| + if( canExecute( TYPESETTER ) ) { | ||
| + System.out.println( "CONTEXT IS CONFIGURED" ); | ||
| + System.out.println( "EXPORT AS: " + mExportPath ); | ||
| + } | ||
| + | ||
| + return null; | ||
| + } | ||
| + | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private boolean canExecute( final String exe ) { | ||
| + final var paths = getenv( "PATH" ).split( quote( pathSeparator ) ); | ||
| + return Stream.of( paths ).map( Paths::get ).anyMatch( | ||
| + path -> { | ||
| + final var p = path.resolve( exe ); | ||
| + final var extensions = new String[]{"", ".com", ".exe", ".bat", ".cmd"}; | ||
| + var found = false; | ||
| + | ||
| + for( final var extension : extensions ) { | ||
| + final var f = new File( p.toString() + extension ); | ||
| + | ||
| + if( f.exists() && f.canExecute() ) { | ||
| + found = true; | ||
| + break; | ||
| + } | ||
| + } | ||
| + | ||
| + return found; | ||
| + } | ||
| + ); | ||
| + } | ||
| +} | ||
| import com.keenwrite.preview.HtmlPreview; | ||
| +import java.io.File; | ||
| import java.nio.file.Path; | ||
| import java.util.Map; | ||
| private final Path mDocumentPath; | ||
| private final Caret mCaret; | ||
| + private final File mExportPath; | ||
| private final ExportFormat mExportFormat; | ||
| private final Workspace mWorkspace; | ||
| * @param caret Location of the caret in the edited document, which is | ||
| * used to synchronize the scrollbars. | ||
| + * @param exportPath Fully qualified filename to use when exporting. | ||
| * @param exportFormat Indicate configuration options for export format. | ||
| + * @param workspace Persistent user preferences settings. | ||
| */ | ||
| public ProcessorContext( | ||
| final HtmlPreview htmlPreview, | ||
| final Map<String, String> resolvedMap, | ||
| final Path documentPath, | ||
| final Caret caret, | ||
| + final File exportPath, | ||
| final ExportFormat exportFormat, | ||
| final Workspace workspace ) { | ||
| mDocumentPath = documentPath; | ||
| mCaret = caret; | ||
| + mExportPath = exportPath; | ||
| mExportFormat = exportFormat; | ||
| mWorkspace = workspace; | ||
| Map<String, String> getResolvedMap() { | ||
| return mResolvedMap; | ||
| + } | ||
| + | ||
| + public File getExportPath() { | ||
| + return mExportPath; | ||
| } | ||
| import com.keenwrite.processors.markdown.MarkdownProcessor; | ||
| -import static com.keenwrite.ExportFormat.NONE; | ||
| -import static com.keenwrite.ExportFormat.XHTML_TEX; | ||
| +import static com.keenwrite.ExportFormat.*; | ||
| +import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | ||
| /** | ||
| : context.isExportFormat( XHTML_TEX ) | ||
| ? createXhtmlProcessor( context.getWorkspace() ) | ||
| + : context.isExportFormat( APPLICATION_PDF ) | ||
| + ? createPdfProcessor( context ) | ||
| : createIdentityProcessor(); | ||
| */ | ||
| private Processor<String> createIdentityProcessor() { | ||
| - return IdentityProcessor.IDENTITY; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Instantiates a new {@link Processor} that wraps an HTML document into | ||
| - * its final, well-formed state (including head and body tags). This is | ||
| - * useful for generating XHTML documents suitable for typesetting (using | ||
| - * an engine such as LuaTeX). | ||
| - * | ||
| - * @return An instance of {@link Processor} that completes an HTML document. | ||
| - */ | ||
| - private Processor<String> createXhtmlProcessor( final Workspace workspace ) { | ||
| - return new XhtmlProcessor( workspace ); | ||
| + return IDENTITY; | ||
| } | ||
| final var xmlp = new XmlProcessor( successor, getProcessorContext() ); | ||
| return createDefinitionProcessor( xmlp ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Instantiates a new {@link Processor} that wraps an HTML document into | ||
| + * its final, well-formed state (including head and body tags). This is | ||
| + * useful for generating XHTML documents suitable for typesetting (using | ||
| + * an engine such as LuaTeX). | ||
| + * | ||
| + * @return An instance of {@link Processor} that completes an HTML document. | ||
| + */ | ||
| + private Processor<String> createXhtmlProcessor( final Workspace workspace ) { | ||
| + return createXhtmlProcessor( IDENTITY, workspace ); | ||
| + } | ||
| + | ||
| + private Processor<String> createXhtmlProcessor( | ||
| + final Processor<String> successor, final Workspace workspace ) { | ||
| + return new XhtmlProcessor( successor, workspace ); | ||
| + } | ||
| + | ||
| + private Processor<String> createPdfProcessor( | ||
| + final ProcessorContext context ) { | ||
| + final var pdfp = new PdfProcessor( context.getExportPath() ); | ||
| + return createXhtmlProcessor( pdfp, context.getWorkspace() ); | ||
| } | ||
| private final Workspace mWorkspace; | ||
| - public XhtmlProcessor( final Workspace workspace ) { | ||
| + public XhtmlProcessor( | ||
| + final Processor<String> successor, final Workspace workspace ) { | ||
| + super( successor ); | ||
| mWorkspace = workspace; | ||
| } | ||
| MediaType mediaType; | ||
| Path imageFile = null; | ||
| - InputStream svgIn ; | ||
| + InputStream svgIn; | ||
| final var protocol = ProtocolScheme.getProtocol( src ); | ||
| public class TexNodeRenderer { | ||
| + private static final RendererFacade RENDERER = new TexElementNodeRenderer(); | ||
| + | ||
| private static final Map<ExportFormat, RendererFacade> EXPORT_RENDERERS = | ||
| Map.of( | ||
| + APPLICATION_PDF, new TexElementNodeRenderer(), | ||
| HTML_TEX_SVG, new TexSvgNodeRenderer(), | ||
| HTML_TEX_DELIMITED, new TexDelimNodeRenderer(), | ||
| XHTML_TEX, new TexElementNodeRenderer(), | ||
| MARKDOWN_PLAIN, new TexDelimNodeRenderer(), | ||
| - NONE, new TexElementNodeRenderer() | ||
| + NONE, RENDERER | ||
| ); | ||
| public static class Factory implements NodeRendererFactory { | ||
| private final RendererFacade mNodeRenderer; | ||
| public Factory( | ||
| final ExportFormat exportFormat, final Processor<String> processor ) { | ||
| - mNodeRenderer = EXPORT_RENDERERS.get( exportFormat ); | ||
| + mNodeRenderer = EXPORT_RENDERERS.getOrDefault( exportFormat, RENDERER ); | ||
| mNodeRenderer.setProcessor( processor ); | ||
| } | ||
| @NotNull | ||
| @Override | ||
| - public NodeRenderer apply( @NotNull DataHolder options ) { | ||
| + public NodeRenderer apply( @NotNull final DataHolder options ) { | ||
| return mNodeRenderer; | ||
| } |
| } | ||
| + // Do not display mnemonic accelerator character in tooltip text. | ||
| + // The accelerator key will still be available, this is display-only. | ||
| + tooltip = tooltip.replace( "_", "" ); | ||
| + | ||
| if( mAccelerator != null ) { | ||
| tooltip += " (" + mAccelerator.getDisplayText() + ')'; |
| } | ||
| + private void file‿export( final ExportFormat format ) { | ||
| + final var main = getMainPane(); | ||
| + final var editor = main.getActiveTextEditor(); | ||
| + final var filename = format.toExportFilename( editor.getPath() ); | ||
| + final var chooser = createFileChooser(); | ||
| + final var selection = chooser.exportAs( filename ); | ||
| + | ||
| + selection.ifPresent( ( file ) -> { | ||
| + final var doc = editor.getText(); | ||
| + final var context = main.createProcessorContext( file, format ); | ||
| + final var chain = createProcessors( context ); | ||
| + final var export = chain.apply( doc ); | ||
| + | ||
| + try { | ||
| + // Processors can export in binary formats that are incompatible with | ||
| + // Java language String objects. In such cases, the processor will | ||
| + // return the null sentinel to signal no further processing is needed. | ||
| + if( export != null ) { | ||
| + writeString( file.toPath(), export ); | ||
| + } | ||
| + | ||
| + clue( get( "Main.status.export.success", file.toString() ) ); | ||
| + } catch( final Exception ex ) { | ||
| + clue( ex ); | ||
| + } | ||
| + } ); | ||
| + } | ||
| + | ||
| + public void file‿export‿pdf() { file‿export( APPLICATION_PDF ); } | ||
| + | ||
| public void file‿export‿html_svg() { | ||
| file‿export( HTML_TEX_SVG ); | ||
| public void file‿export‿markdown() { | ||
| file‿export( MARKDOWN_PLAIN ); | ||
| - } | ||
| - | ||
| - private void file‿export( final ExportFormat format ) { | ||
| - final var main = getMainPane(); | ||
| - final var context = main.createProcessorContext( format ); | ||
| - final var chain = createProcessors( context ); | ||
| - final var editor = main.getActiveTextEditor(); | ||
| - final var doc = editor.getText(); | ||
| - final var export = chain.apply( doc ); | ||
| - final var filename = format.toExportFilename( editor.getPath() ); | ||
| - final var chooser = createFileChooser(); | ||
| - final var file = chooser.exportAs( filename ); | ||
| - | ||
| - file.ifPresent( ( f ) -> { | ||
| - try { | ||
| - writeString( f.toPath(), export ); | ||
| - clue( get( "Main.status.export.success", f.toString() ) ); | ||
| - } catch( final Exception ex ) { | ||
| - clue( ex ); | ||
| - } | ||
| - } ); | ||
| } | ||
| addAction( "file.export", e -> {} ) | ||
| .addSubActions( | ||
| + addAction( "file.export.pdf", e -> actions.file‿export‿pdf() ), | ||
| addAction( "file.export.html_svg", e -> actions.file‿export‿html_svg() ), | ||
| addAction( "file.export.html_tex", e -> actions.file‿export‿html_tex() ), | ||
| getAction( "file.open" ), | ||
| getAction( "file.save" ), | ||
| + SEPARATOR_ACTION, | ||
| + getAction( "file.export.pdf" ), | ||
| SEPARATOR_ACTION, | ||
| getAction( "edit.undo" ), | ||
| Action.file.save_all.text=Save A_ll | ||
| +Action.file.export.pdf.description=Typeset the document | ||
| +Action.file.export.pdf.text=_PDF | ||
| +Action.file.export.pdf.icon=FILE_PDF_ALT | ||
| + | ||
| Action.file.export.html_svg.description=Export the current document as HTML + SVG | ||
| Action.file.export.text=_Export As |
| /* ITEMIZED LISTS ***/ | ||
| - | ||
| ol, ul { | ||
| margin: 0 0 0 2em; |
| \startxmlsetups xml:tex | ||
| - \mathematics{\xmlflushcontext{#1}} | ||
| + %\mathematics{\xmlflushcontext{#1}} | ||
| + \xmlflushcontext{#1} | ||
| \stopxmlsetups | ||
| Delta | 180 lines added, 53 lines removed, 127-line increase |
|---|