Compare commits

...
4 Commits
Author SHA1 Message Date
amitwhandClaude cb98549db8 Release v1.9.1: ASCII Art & Code Block Preservation
Fixed export of ASCII art, charts, tables, and flowcharts to match preview exactly:

🎨 ASCII Art Preservation:
- Code blocks now export exactly as shown in preview
- Each line rendered separately to preserve exact spacing and alignment
- Monospace font (Consolas) used consistently across all export formats
- Line breaks, spacing, and box-drawing characters preserved perfectly

📝 Word Template Exporter Improvements:
- createCodeBlockXml: Renders each line as separate paragraph with exact spacing
- Added <w:keepLines/> and <w:wordWrap w:val="0"/> for no-wrap behavior
- Enhanced isAsciiArt detection with more Unicode box-drawing characters
- Added support for rounded corners (╭╮╯╰), heavy box chars, more arrows

📄 PDF Export Enhancements:
- Added -V monofont="Consolas" for proper code block rendering
- Added --highlight-style=tango for syntax highlighting
- Applied to main export, fallback engines, and CLI conversion

🖨️ HTML/Electron PDF Improvements:
- Enhanced CSS: white-space: pre, word-wrap: normal
- font-family: Consolas, Monaco, 'Courier New', monospace
- Print media queries preserve code block formatting
- overflow: visible prevents content truncation

🔧 Technical Changes:
- src/wordTemplateExporter.js: Enhanced code block and ASCII art rendering
- src/main.js: Added monofont settings to all PDF export paths
- Updated version to 1.9.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 09:01:17 +05:30
amitwh ff651e9b21 Update Claude Code permissions settings 2025-11-08 17:39:14 +05:30
amitwhandClaude e013e5fd69 Fix v1.9.0 logo upload and add MiKTeX PATH support
This commit fixes the logo upload issue and adds MiKTeX to the app's PATH for LaTeX/PDF support.

**Logo Upload Fix:**
- Replaced file input with Browse button that uses dialog.showOpenDialog
- Added browse-header-footer-logo IPC handler in main process
- Now gets reliable file paths without depending on file.path property
- Added browse-btn CSS styling

**MiKTeX PATH Addition:**
- Automatically adds MiKTeX bin directory to process.env.PATH on Windows
- Enables XeLaTeX/PDFLaTeX for PDF exports with headers/footers
- Logs PATH addition for debugging

**Files Changed:**
- src/main.js: Added MiKTeX PATH setup, browse logo IPC handler
- src/renderer.js: Changed to browseForLogo() function, updated event listeners
- src/index.html: Replaced file inputs with browse buttons
- src/styles.css: Added .browse-btn styling

**Testing Notes:**
- Logo upload now works without "path undefined" errors
- DOCX exports with headers/footers confirmed working
- PDF exports should now use Pandoc+XeLaTeX with MiKTeX in PATH
- Requires MiKTeX installation and PC restart for full functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 19:33:36 +05:30
amitwhandClaude f7362bec46 Fix v1.9.0 header/footer visibility issues
This commit fixes two critical issues in the v1.9.0 header/footer feature:

1. **Logo Upload Error Fix**:
   - Fixed "path undefined" error when uploading logos
   - Added validation in main.js to check filePath before processing
   - Added fallback in renderer.js to use webUtils.getPathForFile if file.path is undefined
   - Added file existence check before copying

2. **Header/Footer Visibility Fix**:
   - Added header/footer support to tryPdfFallback function (PDF fallback engines)
   - Previously, only performExportWithOptions had header/footer logic
   - Now all PDF export paths include headers/footers when enabled
   - Fallback engines now generate fancyhdr LaTeX code for headers/footers

Technical details:
- src/main.js:667-709: Enhanced logo upload handler with error checking
- src/main.js:1289-1319: Added header/footer to tryPdfFallback function
- src/renderer.js:2661-2678: Fixed logo file path handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 17:41:37 +05:30
8 changed files with 385 additions and 89 deletions
File diff suppressed because one or more lines are too long
+83 -13
View File
@@ -4,7 +4,7 @@
**PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options. **PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options.
**Current Version**: v1.9.0 **Current Version**: v1.9.1
**Author**: Amit Haridas (amit.wh@gmail.com) **Author**: Amit Haridas (amit.wh@gmail.com)
**License**: MIT **License**: MIT
**Repository**: https://github.com/amitwh/pan-converter **Repository**: https://github.com/amitwh/pan-converter
@@ -113,7 +113,77 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
## Feature Implementation Guide ## Feature Implementation Guide
### v1.9.0 Custom Headers & Footers (Latest) ### v1.9.1 ASCII Art & Code Block Preservation (Latest)
#### 🎨 Preserved ASCII Art in Exports
**ASCII Art, Charts, Tables & Flowcharts Now Export Exactly as Previewed** (`src/wordTemplateExporter.js`, `src/main.js`)
**Problem Solved:**
- Previously, ASCII art, flowcharts, and code blocks were being corrupted during export to Word and PDF
- Line breaks, spacing, and monospace alignment were lost
- Box-drawing characters and diagrams became unreadable
**Solution Implemented:**
1. **Enhanced Word Template Exporter** (`src/wordTemplateExporter.js:259-289`)
- Code blocks now render each line as a separate paragraph with exact spacing
- `white-space: pre` and `word-wrap: normal` prevent text wrapping
- Monospace font (Consolas) with consistent 18pt size
- Light gray background (#F5F5F5) for visual distinction
- `<w:keepLines/>` and `<w:wordWrap w:val="0"/>` preserve exact formatting
2. **Improved ASCII Art Detection** (`src/wordTemplateExporter.js:410-456`)
- Extended Unicode box-drawing character support (╭╮╯╰ rounded corners, heavy box characters)
- Additional arrow characters (↔↕⇒⇐⇓⇑)
- More ASCII patterns detected (+---+---+, <---->, etc.)
- Better markdown table vs ASCII art differentiation
3. **Enhanced PDF Export** (`src/main.js:1266-1268`, `src/main.js:1345-1347`, `src/main.js:2410-2412`)
- Added `-V monofont="Consolas"` for monospace code blocks
- Added `--highlight-style=tango` for syntax highlighting
- Applied to main export, fallback export, and CLI conversion
4. **Electron PDF Fallback** (`src/main.js:1613-1637`)
- Enhanced CSS for `<pre>` and `<code>` elements
- `white-space: pre` prevents wrapping
- `font-family: Consolas, Monaco, 'Courier New', monospace`
- Print media query preserves formatting when printing
5. **HTML Export** (`src/main.js:1525-1548`)
- Same CSS improvements as Electron PDF
- Code blocks maintain exact preview appearance
**Key CSS Properties for ASCII Preservation:**
```css
pre {
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
pre code {
white-space: pre;
word-wrap: normal;
display: block;
}
```
**Word XML Properties for ASCII Preservation:**
```xml
<w:pPr>
<w:spacing w:line="240" w:lineRule="exact"/>
<w:keepLines/>
<w:wordWrap w:val="0"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:noProof/>
</w:rPr>
```
---
### v1.9.0 Custom Headers & Footers
#### 📄 Comprehensive Header & Footer System #### 📄 Comprehensive Header & Footer System
**Custom Headers & Footers for Standard Exports** (`src/main.js:701-853`, `src/index.html:929-1042`, `src/renderer.js:2560-2747`, `src/styles.css:3162-3432`) **Custom Headers & Footers for Standard Exports** (`src/main.js:701-853`, `src/index.html:929-1042`, `src/renderer.js:2560-2747`, `src/styles.css:3162-3432`)
@@ -1109,7 +1179,7 @@ if (!gotTheLock) {
## Pending Tasks & Future Enhancements ## Pending Tasks & Future Enhancements
No pending tasks at this time. All planned v1.9.0 features have been completed. No pending tasks at this time. All planned v1.9.1 features have been completed.
### Potential Future Enhancements ### Potential Future Enhancements
- Image embedding in headers/footers (partially implemented - logo upload UI ready) - Image embedding in headers/footers (partially implemented - logo upload UI ready)
@@ -1119,19 +1189,19 @@ No pending tasks at this time. All planned v1.9.0 features have been completed.
--- ---
**Last Updated**: October 28, 2025 **Last Updated**: December 12, 2025
**Claude Assistant**: Development completed for v1.9.0 with Custom Headers & Footers! **Claude Assistant**: Development completed for v1.9.1 with ASCII Art Preservation!
### v1.9.0 - Latest Release Summary ### v1.9.1 - Latest Release Summary
- **Custom Headers & Footers**: Professional headers/footers for PDF, DOCX, ODT, and PowerPoint exports - **ASCII Art Preservation**: Code blocks, flowcharts, tables, and ASCII art now export exactly as shown in preview
- **Dynamic Field Support**: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$ - **Enhanced Code Block Rendering**: Each line rendered separately to preserve exact spacing and alignment
- **Logo Upload UI**: Ready for image embedding in headers/footers - **Monospace Font Support**: Consolas font used consistently across all export formats
- **Three-Column Layout**: Left/Center/Right positioning for headers and footers - **Improved ASCII Detection**: Extended Unicode box-drawing and arrow character support
- **Batch Support**: Full header/footer integration in batch converter - **PDF Export Enhancement**: Added monofont and highlight-style options for better code rendering
- **Persistent Settings**: Configurations saved across sessions - **HTML/Electron PDF Fix**: Enhanced CSS to prevent text wrapping in code blocks
- **Dark Theme Support**: Complete styling for all 22 themes
### Previous Releases ### Previous Releases
- v1.9.0: Custom Headers & Footers for PDF, DOCX, ODT, PowerPoint exports
- v1.8.3: Streamlined PDF Editor UI & Configurable Template Settings - v1.8.3: Streamlined PDF Editor UI & Configurable Template Settings
- v1.8.2: Enhanced PDF Export & Print Fix - v1.8.2: Enhanced PDF Export & Print Fix
- v1.8.1: Streamlined PDF Editor UI - v1.8.1: Streamlined PDF Editor UI
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pan-converter", "name": "pan-converter",
"version": "1.9.0", "version": "1.9.1",
"description": "Cross-platform Markdown editor and converter using Pandoc", "description": "Cross-platform Markdown editor and converter using Pandoc",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
+2 -2
View File
@@ -964,7 +964,7 @@
</div> </div>
<div class="hf-logo-row"> <div class="hf-logo-row">
<label>Header Logo/Image:</label> <label>Header Logo/Image:</label>
<input type="file" id="header-logo" accept="image/*"> <button id="header-logo-browse" class="browse-btn">Browse...</button>
<button id="header-logo-clear" class="clear-btn">Clear</button> <button id="header-logo-clear" class="clear-btn">Clear</button>
<span id="header-logo-preview" class="logo-preview"></span> <span id="header-logo-preview" class="logo-preview"></span>
</div> </div>
@@ -992,7 +992,7 @@
</div> </div>
<div class="hf-logo-row"> <div class="hf-logo-row">
<label>Footer Logo/Image:</label> <label>Footer Logo/Image:</label>
<input type="file" id="footer-logo" accept="image/*"> <button id="footer-logo-browse" class="browse-btn">Browse...</button>
<button id="footer-logo-clear" class="clear-btn">Clear</button> <button id="footer-logo-clear" class="clear-btn">Clear</button>
<span id="footer-logo-preview" class="logo-preview"></span> <span id="footer-logo-preview" class="logo-preview"></span>
</div> </div>
+212 -6
View File
@@ -5,6 +5,15 @@ const { exec } = require('child_process');
const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib'); const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
const WordTemplateExporter = require('./wordTemplateExporter'); const WordTemplateExporter = require('./wordTemplateExporter');
// Add MiKTeX to PATH for LaTeX support
if (process.platform === 'win32') {
const miktexPath = 'C:\\Program Files\\MiKTeX\\miktex\\bin\\x64';
if (fs.existsSync(miktexPath)) {
process.env.PATH = `${miktexPath};${process.env.PATH}`;
console.log('[MAIN] Added MiKTeX to PATH:', miktexPath);
}
}
// Get the system Pandoc path // Get the system Pandoc path
function getPandocPath() { function getPandocPath() {
// Pandoc is expected to be in the system's PATH. // Pandoc is expected to be in the system's PATH.
@@ -506,7 +515,7 @@ function createMenu() {
type: 'info', type: 'info',
title: 'About PanConverter', title: 'About PanConverter',
message: 'PanConverter', message: 'PanConverter',
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics', detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.1\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Preserved ASCII art, charts, tables, flowcharts in exports (NEW in v1.9.1)\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
buttons: ['OK'] buttons: ['OK']
}); });
} }
@@ -664,8 +673,59 @@ ipcMain.on('save-header-footer-settings', (event, settings) => {
}); });
// Save header/footer logo image // Save header/footer logo image
// Browse for header/footer logo
ipcMain.on('browse-header-footer-logo', async (event, position) => {
try {
const result = await dialog.showOpenDialog(mainWindow, {
title: `Select ${position.charAt(0).toUpperCase() + position.slice(1)} Logo/Image`,
filters: [
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp'] }
],
properties: ['openFile']
});
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
// Copy image to userData directory for persistent storage
const userDataPath = app.getPath('userData');
const logoDir = path.join(userDataPath, 'logos');
// Create logos directory if it doesn't exist
if (!fs.existsSync(logoDir)) {
fs.mkdirSync(logoDir, { recursive: true });
}
// Generate unique filename
const ext = path.extname(filePath);
const filename = `${position}_${Date.now()}${ext}`;
const destPath = path.join(logoDir, filename);
// Copy file
fs.copyFileSync(filePath, destPath);
// Update settings
if (position === 'header') {
headerFooterSettings.header.logo = destPath;
} else if (position === 'footer') {
headerFooterSettings.footer.logo = destPath;
}
event.reply('header-footer-logo-saved', { position, path: destPath });
}
} catch (error) {
console.error('Logo browse error:', error);
dialog.showErrorBox('Logo Error', `Failed to select logo: ${error.message}`);
}
});
ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => { ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
try { try {
if (!filePath) {
dialog.showErrorBox('Logo Error', 'Failed to save logo: The "path" argument must be of type string. Received undefined');
return;
}
// Copy image to userData directory for persistent storage // Copy image to userData directory for persistent storage
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath('userData');
const logoDir = path.join(userDataPath, 'logos'); const logoDir = path.join(userDataPath, 'logos');
@@ -675,6 +735,12 @@ ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
fs.mkdirSync(logoDir, { recursive: true }); fs.mkdirSync(logoDir, { recursive: true });
} }
// Verify source file exists
if (!fs.existsSync(filePath)) {
dialog.showErrorBox('Logo Error', `Source file not found: ${filePath}`);
return;
}
// Generate unique filename // Generate unique filename
const ext = path.extname(filePath); const ext = path.extname(filePath);
const filename = `${position}_${Date.now()}${ext}`; const filename = `${position}_${Date.now()}${ext}`;
@@ -692,6 +758,7 @@ ipcMain.on('save-header-footer-logo', async (event, { position, filePath }) => {
event.reply('header-footer-logo-saved', { position, path: destPath }); event.reply('header-footer-logo-saved', { position, path: destPath });
} catch (error) { } catch (error) {
console.error('Logo save error:', error);
dialog.showErrorBox('Logo Error', `Failed to save logo: ${error.message}`); dialog.showErrorBox('Logo Error', `Failed to save logo: ${error.message}`);
} }
}); });
@@ -1196,6 +1263,42 @@ function performExportWithOptions(format, options) {
pandocCmd += ` --pdf-engine="${pdfEngine}"`; pandocCmd += ` --pdf-engine="${pdfEngine}"`;
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`; if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add header/footer if enabled
if (headerFooterSettings.enabled) {
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
const metadata = { filename, title: filename, author: '' };
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
// Create LaTeX header
const latexHeader = `
\\usepackage{fancyhdr}
\\pagestyle{fancy}
\\fancyhf{}
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
\\cfoot{${footerCenter.replace(/[$]PAGE[$]/g, '\\\\thepage').replace(/[$]TOTAL[$]/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
\\renewcommand{\\headrulewidth}{0.4pt}
\\renewcommand{\\footrulewidth}{0.4pt}
`;
const headerFile = path.join(require('os').tmpdir(), `header_export_${Date.now()}.tex`);
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
pandocCmd += ` --include-in-header="${headerFile}"`;
pandocCmd += ' --variable header-includes="\\\\usepackage{lastpage}"';
}
// Try with specified PDF engine // Try with specified PDF engine
exec(pandocCmd, (error) => { exec(pandocCmd, (error) => {
if (error) { if (error) {
@@ -1209,6 +1312,15 @@ function performExportWithOptions(format, options) {
} else if (format === 'docx') { } else if (format === 'docx') {
pandocCmd += ' -t docx'; pandocCmd += ' -t docx';
exportWithPandoc(pandocCmd, outputFile, format); exportWithPandoc(pandocCmd, outputFile, format);
} else if (format === 'pptx') {
// Add PowerPoint footer if enabled
if (headerFooterSettings.enabled && headerFooterSettings.footer.center) {
const filename = currentFile ? path.basename(currentFile, path.extname(currentFile)) : 'document';
const metadata = { filename, title: filename, author: '' };
const footerText = processDynamicFields(headerFooterSettings.footer.center, metadata);
pandocCmd += ` --variable footer="${footerText}"`;
}
exportWithPandoc(pandocCmd, outputFile, format);
} else { } else {
// Generic export for other formats // Generic export for other formats
exportWithPandoc(pandocCmd, outputFile, format); exportWithPandoc(pandocCmd, outputFile, format);
@@ -1230,9 +1342,45 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro
const engine = engines[index]; const engine = engines[index];
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`; let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
// Add monospace font settings for code blocks (ASCII art preservation)
pandocCmd += ' -V monofont="Consolas"';
pandocCmd += ' --highlight-style=tango';
// Add geometry if specified // Add geometry if specified
if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `); if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `);
// Add header/footer if enabled
if (headerFooterSettings.enabled) {
const filename = path.basename(inputFile, path.extname(inputFile));
const metadata = { filename, title: filename, author: options.metadata?.author || '' };
const headerLeft = processDynamicFields(headerFooterSettings.header.left, metadata);
const headerCenter = processDynamicFields(headerFooterSettings.header.center, metadata);
const headerRight = processDynamicFields(headerFooterSettings.header.right, metadata);
const footerLeft = processDynamicFields(headerFooterSettings.footer.left, metadata);
const footerCenter = processDynamicFields(headerFooterSettings.footer.center, metadata);
const footerRight = processDynamicFields(headerFooterSettings.footer.right, metadata);
// Create LaTeX header for fancyhdr
const latexHeader = `
\\usepackage{fancyhdr}
\\usepackage{lastpage}
\\pagestyle{fancy}
\\fancyhf{}
\\lhead{${headerLeft.replace(/\\/g, '\\\\')}}
\\chead{${headerCenter.replace(/\\/g, '\\\\')}}
\\rhead{${headerRight.replace(/\\/g, '\\\\')}}
\\lfoot{${footerLeft.replace(/\\/g, '\\\\')}}
\\cfoot{${footerCenter.replace(/\$PAGE\$/g, '\\\\thepage').replace(/\$TOTAL\$/g, '\\\\pageref{LastPage}').replace(/\\/g, '\\\\')}}
\\rfoot{${footerRight.replace(/\\/g, '\\\\')}}
\\renewcommand{\\headrulewidth}{0.4pt}
\\renewcommand{\\footrulewidth}{0.4pt}
`;
const headerFile = path.join(require('os').tmpdir(), `header_fallback_${Date.now()}.tex`);
fs.writeFileSync(headerFile, latexHeader, 'utf-8');
pandocCmd += ` --include-in-header="${headerFile}"`;
}
// Add all other options // Add all other options
if (options.template && options.template !== 'default') { if (options.template && options.template !== 'default') {
pandocCmd += ` --template="${options.template}"`; pandocCmd += ` --template="${options.template}"`;
@@ -1354,7 +1502,7 @@ function exportToHTML(outputFile) {
const marked = require('marked'); const marked = require('marked');
const markdownContent = fs.readFileSync(currentFile, 'utf8'); const markdownContent = fs.readFileSync(currentFile, 'utf8');
const htmlContent = marked.parse(markdownContent); const htmlContent = marked.parse(markdownContent);
const fullHtml = `<!DOCTYPE html> const fullHtml = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -1374,21 +1522,37 @@ function exportToHTML(outputFile) {
margin-top: 1.5em; margin-top: 1.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
/* Inline code */
code { code {
background: #f4f4f4; background: #f4f4f4;
padding: 2px 4px; padding: 2px 4px;
border-radius: 3px; border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace; font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 0.9em;
} }
/* Code blocks - critical for ASCII art preservation */
pre { pre {
background: #f4f4f4; background: #f5f5f5;
padding: 1em; padding: 1em;
border-radius: 5px; border-radius: 5px;
overflow-x: auto; overflow-x: auto;
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
border: 1px solid #e0e0e0;
margin: 1em 0;
} }
pre code { pre code {
background: transparent; background: transparent;
padding: 0; padding: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
white-space: pre;
word-wrap: normal;
display: block;
} }
blockquote { blockquote {
border-left: 4px solid #ddd; border-left: 4px solid #ddd;
@@ -1420,6 +1584,18 @@ function exportToHTML(outputFile) {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
@media print {
pre {
white-space: pre;
word-wrap: normal;
overflow: visible;
page-break-inside: avoid;
}
pre code {
white-space: pre;
word-wrap: normal;
}
}
</style> </style>
</head> </head>
<body> <body>
@@ -1442,7 +1618,7 @@ function exportToPDFElectron(outputFile) {
const marked = require('marked'); const marked = require('marked');
const markdownContent = fs.readFileSync(currentFile, 'utf8'); const markdownContent = fs.readFileSync(currentFile, 'utf8');
const htmlContent = marked.parse(markdownContent); const htmlContent = marked.parse(markdownContent);
const fullHtml = `<!DOCTYPE html> const fullHtml = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -1462,21 +1638,38 @@ function exportToPDFElectron(outputFile) {
margin-top: 1.5em; margin-top: 1.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
/* Inline code */
code { code {
background: #f4f4f4; background: #f4f4f4;
padding: 2px 4px; padding: 2px 4px;
border-radius: 3px; border-radius: 3px;
font-family: Consolas, Monaco, 'Courier New', monospace; font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 0.9em;
} }
/* Code blocks - critical for ASCII art preservation */
pre { pre {
background: #f4f4f4; background: #f5f5f5;
padding: 1em; padding: 1em;
border-radius: 5px; border-radius: 5px;
overflow-x: auto; overflow-x: visible;
overflow-y: visible;
white-space: pre;
word-wrap: normal;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
border: 1px solid #e0e0e0;
margin: 1em 0;
} }
pre code { pre code {
background: transparent; background: transparent;
padding: 0; padding: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
white-space: pre;
word-wrap: normal;
display: block;
} }
blockquote { blockquote {
border-left: 4px solid #ddd; border-left: 4px solid #ddd;
@@ -1503,6 +1696,16 @@ function exportToPDFElectron(outputFile) {
} }
@media print { @media print {
body { padding: 20px; } body { padding: 20px; }
pre {
white-space: pre;
word-wrap: normal;
overflow: visible;
page-break-inside: avoid;
}
pre code {
white-space: pre;
word-wrap: normal;
}
} }
</style> </style>
</head> </head>
@@ -2204,6 +2407,9 @@ function buildPandocCommand(content, format, outputPath) {
switch (format) { switch (format) {
case 'pdf': case 'pdf':
command += ' --pdf-engine=xelatex --variable geometry:margin=1in'; command += ' --pdf-engine=xelatex --variable geometry:margin=1in';
// Add monospace font settings for code blocks (ASCII art preservation)
command += ' -V monofont="Consolas"';
command += ' --highlight-style=tango';
// Add header/footer if enabled // Add header/footer if enabled
if (headerFooterSettings.enabled) { if (headerFooterSettings.enabled) {
+6 -14
View File
@@ -2658,16 +2658,9 @@ function saveHeaderFooterSettings() {
closeHeaderFooterDialog(); closeHeaderFooterDialog();
} }
// Handle logo file selection // Handle logo browsing - ask main process to show open dialog
function handleLogoSelection(position) { function browseForLogo(position) {
const input = document.getElementById(`${position}-logo`); ipcRenderer.send('browse-header-footer-logo', position);
if (input.files && input.files[0]) {
const filePath = input.files[0].path;
// Send to main process to save
ipcRenderer.send('save-header-footer-logo', { position, filePath });
}
} }
// Handle logo saved confirmation // Handle logo saved confirmation
@@ -2678,7 +2671,6 @@ ipcRenderer.on('header-footer-logo-saved', (event, { position, path }) => {
// Clear logo // Clear logo
function clearLogo(position) { function clearLogo(position) {
ipcRenderer.send('clear-header-footer-logo', position); ipcRenderer.send('clear-header-footer-logo', position);
document.getElementById(`${position}-logo`).value = '';
document.getElementById(`${position}-logo-preview`).textContent = ''; document.getElementById(`${position}-logo-preview`).textContent = '';
} }
@@ -2721,9 +2713,9 @@ document.querySelectorAll('.field-insert-btn').forEach(btn => {
}); });
}); });
// Logo file inputs // Logo browse buttons
document.getElementById('header-logo').addEventListener('change', () => handleLogoSelection('header')); document.getElementById('header-logo-browse').addEventListener('click', () => browseForLogo('header'));
document.getElementById('footer-logo').addEventListener('change', () => handleLogoSelection('footer')); document.getElementById('footer-logo-browse').addEventListener('click', () => browseForLogo('footer'));
// Logo clear buttons // Logo clear buttons
document.getElementById('header-logo-clear').addEventListener('click', () => clearLogo('header')); document.getElementById('header-logo-clear').addEventListener('click', () => clearLogo('header'));
+16
View File
@@ -3312,6 +3312,22 @@ body.printing-no-styles .preview-content pre,
background: var(--button-hover, #5a6268); background: var(--button-hover, #5a6268);
} }
.browse-btn {
padding: 6px 12px;
background: var(--accent-color, #007bff);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s ease;
margin-right: 8px;
}
.browse-btn:hover {
background: var(--accent-hover, #0056b3);
}
.clear-btn { .clear-btn {
padding: 6px 12px; padding: 6px 12px;
background: var(--danger-color, #dc3545); background: var(--danger-color, #dc3545);
+47 -20
View File
@@ -253,23 +253,39 @@ class WordTemplateExporter {
} }
/** /**
* Create code block XML * Create code block XML - renders each line as separate paragraph
* to preserve exact formatting like in preview (monospace, no wrapping)
*/ */
createCodeBlockXml(code) { createCodeBlockXml(code) {
const escapedCode = this.escapeXml(code); const lines = code.split('\n');
let xml = '';
return `<w:p> lines.forEach((line, index) => {
<w:pPr> const escapedLine = this.escapeXml(line);
<w:pStyle w:val="Code"/>
</w:pPr> // Each line gets its own paragraph with exact spacing and no wrapping
<w:r> xml += `<w:p>
<w:rPr> <w:pPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/> <w:spacing w:before="${index === 0 ? '120' : '0'}" w:after="${index === lines.length - 1 ? '120' : '0'}" w:line="240" w:lineRule="exact"/>
<w:sz w:val="18"/> <w:ind w:left="284" w:right="0"/>
</w:rPr> <w:jc w:val="left"/>
<w:t xml:space="preserve">${escapedCode}</w:t> <w:keepLines/>
</w:r> <w:wordWrap w:val="0"/>
</w:p>`; </w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>
<w:sz w:val="18"/>
<w:szCs w:val="18"/>
<w:shd w:val="clear" w:color="auto" w:fill="F5F5F5"/>
<w:noProof/>
</w:rPr>
<w:t xml:space="preserve">${escapedLine}</w:t>
</w:r>
</w:p>`;
});
return xml;
} }
/** /**
@@ -397,15 +413,22 @@ class WordTemplateExporter {
isAsciiArt(line) { isAsciiArt(line) {
// Don't treat markdown tables as ASCII art // Don't treat markdown tables as ASCII art
if (line.trim().startsWith('|') && line.trim().endsWith('|')) { if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
return false; // Check if it's a proper markdown table (has multiple cells)
const cells = line.split('|').filter(c => c.trim());
if (cells.length >= 2) {
return false;
}
} }
// Common ASCII art characters // Common ASCII art characters (Unicode box drawing and symbols)
const asciiArtChars = [ const asciiArtChars = [
'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing '─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing
'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box '═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box
'', '', '', '', '♦', '●', '○', '■', '□', // Shapes '', '', '', '', // Rounded corners
'', '', '', '', // Arrows '', '', '', '', '♦', '●', '○', '■', '□', '◆', '◇', // Shapes
'↓', '→', '←', '↑', '↔', '↕', '⇒', '⇐', '⇓', '⇑', // Arrows
'┃', '━', '┏', '┓', '┗', '┛', '┣', '┫', '┳', '┻', '╋', // Heavy box
'░', '▒', '▓', '█', // Shading
]; ];
// Check for box drawing characters // Check for box drawing characters
@@ -415,14 +438,18 @@ class WordTemplateExporter {
// Check for ASCII box patterns with regular characters // Check for ASCII box patterns with regular characters
const asciiPatterns = [ const asciiPatterns = [
/^\s*\+[-=_]+\+/, // +-----+ /^\s*\+[-=_+]+\+/, // +-----+ or +=====+
/^\s*\|[-=_]{3,}\|/, // |-----| /^\s*\|[-=_]{3,}\|/, // |-----|
/^\s*[-=_]{5,}$/, // ----- /^\s*[-=_]{5,}$/, // -----
/^\s*\+[-]+\+[-]+\+/, // +---+---+
/^\s*\([A-Z][a-z]+\)\s*\|\|/, // (Deformable) || /^\s*\([A-Z][a-z]+\)\s*\|\|/, // (Deformable) ||
/^\s*\|\s+[A-Z\s]+[-:]\s+[A-Z]/, // | TILE ADHESIVE - TYPE /^\s*\|\s+[A-Z\s]+[-:]\s+[A-Z]/, // | TILE ADHESIVE - TYPE
/^\s*\|\s*\[[^\]]+\]/, // | [BIS Mark / CE Mark] /^\s*\|\s*\[[^\]]+\]/, // | [BIS Mark / CE Mark]
/^\s*\|\s{2,}\w+.*\|\|/, // || text || /^\s*\|\s{2,}\w+.*\|\|/, // || text ||
/^\s*\[[^\]]+\]/, // [Step in brackets] /^\s*\[[^\]]+\]\s*$/, // [Step in brackets]
/^\s*<[-=]+>/, // <----> or <====>
/^\s*\/[-_\\\/]+\//, // /----/
/^\s*\*[-=\*]+\*/, // *----*
]; ];
return asciiPatterns.some(pattern => pattern.test(line)); return asciiPatterns.some(pattern => pattern.test(line));