Theme.java
package pro.verron.asciidoc.converters.svg;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static pro.verron.asciidoc.converters.svg.AsciiDocIcon.findIcon;
import static pro.verron.asciidoc.converters.svg.Icon.*;
import static pro.verron.asciidoc.converters.svg.SvgAttribute.attr;
/// Editor theme used by the SVG converter to simulate different word-processor
/// interfaces (Word, Google Docs, LibreOffice).
public enum Theme {
/// No editor chrome; renders only the document content.
NONE,
/// Microsoft Word-style chrome.
WORD,
/// Google Docs-style chrome.
GDOCS,
/// LibreOffice Writer-style chrome.
LIBRE;
/// Returns the stroke color used for comment connectors and element outlines.
///
/// @return the stroke color, or empty if the theme has none
public Optional<String> getStrokeColor() {
return switch (this) {
case NONE -> Optional.empty();
case WORD -> Optional.of("#ffc000");
case GDOCS -> Optional.of("#0b57d0");
case LIBRE -> Optional.of("#808080");
};
}
/// Returns the background highlight color for commented blocks.
///
/// @return the highlight color, or empty if the theme has none
public Optional<String> getHighlightColor() {
return switch (this) {
case NONE -> Optional.empty();
case WORD -> Optional.of("#fff2cc");
case GDOCS -> Optional.of("#c2e7ff");
case LIBRE -> Optional.of("#ffff00");
};
}
/// Returns the overall background color of the simulated editor.
///
/// @return the background color, or empty if the theme has none
public Optional<String> getBgColor() {
return switch (this) {
case NONE -> Optional.empty();
case WORD -> Optional.of("#e6e6e6");
case GDOCS -> Optional.of("#f8f9fa");
case LIBRE -> Optional.of("#dfdfdf");
};
}
/// Returns the SVG font-family string for a given theme.
///
/// @return font-family string
public Optional<String> getFontFamily() {
return switch (this) {
case NONE -> Optional.empty();
case WORD -> Optional.of("Calibri, 'Carlito', 'Arial', sans-serif");
case GDOCS -> Optional.of("Arial, 'Arimo', sans-serif");
case LIBRE -> Optional.of("'Liberation Serif', 'Tinos', 'Times "
+ "New Roman', serif");
};
}
/// Returns the primary font name for AWT font instantiation.
Optional<String> getPrimaryFont() {
return switch (this) {
case NONE -> Optional.empty();
case WORD -> Optional.of("Calibri");
case GDOCS -> Optional.of("Arial");
case LIBRE -> Optional.of("Liberation Serif");
};
}
/// Renders the editor banner (toolbar and menu bar) as a collection of SVG elements.
///
/// @param title document title displayed in the banner
/// @param bannerHeight total height of the banner area in pixels
///
/// @return SVG elements that compose the banner
public Collection<? extends SvgElement> renderBanner(
String title,
double bannerHeight
) {
return switch (this) {
case NONE -> List.of();
case WORD -> renderWordBanner(title, bannerHeight);
case GDOCS -> renderGoogleDocsBanner(title, bannerHeight);
case LIBRE -> renderLibreOfficeBanner(title, bannerHeight);
};
}
private List<SvgElement> renderWordBanner(
String title,
double bannerHeight
) {
var elements = new ArrayList<SvgElement>();
// Blue title bar with Quick Access Toolbar
elements.add(new SvgRect("0",
"0",
"100%",
"30",
attr("fill", "#2b579a")));
elements.add(new SvgText("50%",
"20",
"12",
"white",
title + " - Word",
attr("font-family", "Segoe UI, Arial"),
attr("text-anchor", "middle")));
// Quick Access Toolbar icons
findIcon(SAVE, 10, 7, 16, "white").ifPresent(elements::add);
findIcon(UNDO, 35, 7, 16, "white").ifPresent(elements::add);
findIcon(REDO, 60, 7, 16, "white").ifPresent(elements::add);
// Ribbon area background
elements.add(new SvgRect("0",
"30",
"100%",
String.valueOf(bannerHeight - 30),
attr("fill", "#f3f2f1")));
elements.add(new SvgLine("0", "30", "100%", "30", "#ccc"));
elements.add(new SvgLine("0",
String.valueOf(bannerHeight),
"100%",
String.valueOf(bannerHeight),
"#ccc"));
// Ribbon tab names
elements.add(new SvgText("20",
"55",
"11",
"#333",
"File Home Insert Design Layout References Mailings Review View",
attr("font-family", "Segoe UI, Arial")));
// Clipboard group
findIcon(PASTE, 20, 70, 16, "#333").ifPresent(elements::add);
findIcon(CUT, 45, 70, 14, "#333").ifPresent(elements::add);
findIcon(COPY, 65, 70, 14, "#333").ifPresent(elements::add);
findIcon(FORMAT_PAINTER, 85, 70, 14, "#333").ifPresent(elements::add);
// Font group
findIcon(BOLD, 125, 70, 16, "#333").ifPresent(elements::add);
findIcon(ITALIC, 150, 70, 16, "#333").ifPresent(elements::add);
findIcon(UNDERLINE, 175, 70, 16, "#333").ifPresent(elements::add);
findIcon(STRIKETHROUGH, 200, 70, 14, "#333").ifPresent(elements::add);
findIcon(SUBSCRIPT, 220, 70, 14, "#333").ifPresent(elements::add);
findIcon(SUPERSCRIPT, 240, 70, 14, "#333").ifPresent(elements::add);
findIcon(CLEAR_FORMATTING, 260, 70, 14, "#333").ifPresent(elements::add);
findIcon(HIGHLIGHT, 280, 70, 14, "#333").ifPresent(elements::add);
findIcon(FONT_COLOR, 300, 70, 14, "#333").ifPresent(elements::add);
// Paragraph group
findIcon(BULLETS, 330, 70, 14, "#333").ifPresent(elements::add);
findIcon(NUMBERING, 350, 70, 14, "#333").ifPresent(elements::add);
findIcon(INDENT_DECREASE, 370, 70, 14, "#333").ifPresent(elements::add);
findIcon(INDENT_INCREASE, 390, 70, 14, "#333").ifPresent(elements::add);
findIcon(ALIGN_LEFT, 415, 70, 14, "#333").ifPresent(elements::add);
findIcon(ALIGN_CENTER, 435, 70, 14, "#333").ifPresent(elements::add);
findIcon(ALIGN_RIGHT, 455, 70, 14, "#333").ifPresent(elements::add);
findIcon(JUSTIFY, 475, 70, 14, "#333").ifPresent(elements::add);
// Editing group
findIcon(SEARCH, 510, 70, 14, "#333").ifPresent(elements::add);
return elements;
}
private List<SvgElement> renderGoogleDocsBanner(
String title,
double bannerHeight
) {
var elements = new ArrayList<SvgElement>();
// Top bar
elements.add(new SvgRect("0",
"0",
"100%",
"60",
attr("fill", "white")));
elements.add(new SvgCircle("30", "30", "15", "#4285f4"));
findIcon(TABLE, 22, 22, 16, "white").ifPresent(elements::add);
elements.add(new SvgText("60",
"25",
"18",
"#3c4043",
title,
attr("font-family", "Product Sans, Arial")));
elements.add(new SvgText("60",
"45",
"12",
"#5f6368",
"File Edit View Insert Format Tools Extensions Help",
attr("font-family", "Arial")));
// Star and Share
findIcon(STAR, 520, 18, 16, "#5f6368").ifPresent(elements::add);
findIcon(SHARE, 545, 18, 16, "#5f6368").ifPresent(elements::add);
// Toolbar background
elements.add(new SvgRect("0",
"65",
"100%",
String.valueOf(bannerHeight - 70),
attr("fill", "#edf2fa"),
attr("rx", "15")));
elements.add(new SvgLine("0", "100", "100%", "100", "#ccc"));
// Undo / Redo / Print
findIcon(UNDO, 20, 72, 16, "#5f6368").ifPresent(elements::add);
findIcon(REDO, 45, 72, 16, "#5f6368").ifPresent(elements::add);
findIcon(PRINT, 70, 72, 16, "#5f6368").ifPresent(elements::add);
findIcon(SPELLCHECK, 95, 72, 16, "#5f6368").ifPresent(elements::add);
// Formatting icons
findIcon(BOLD, 135, 72, 16, "#5f6368").ifPresent(elements::add);
findIcon(ITALIC, 160, 72, 16, "#5f6368").ifPresent(elements::add);
findIcon(UNDERLINE, 185, 72, 16, "#5f6368").ifPresent(elements::add);
// Text color
findIcon(FONT_COLOR, 215, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(HIGHLIGHT, 235, 72, 14, "#5f6368").ifPresent(elements::add);
// Link / Comment / Image / Chart
findIcon(LINK, 265, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(COMMENT, 290, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(IMAGE, 315, 72, 14, "#5f6368").ifPresent(elements::add);
// Alignment
findIcon(ALIGN_LEFT, 350, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(ALIGN_CENTER, 370, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(ALIGN_RIGHT, 390, 72, 14, "#5f6368").ifPresent(elements::add);
// Line spacing / Bullets / Numbering
findIcon(BULLETS, 420, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(NUMBERING, 440, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(INDENT_DECREASE, 460, 72, 14, "#5f6368").ifPresent(elements::add);
findIcon(INDENT_INCREASE, 480, 72, 14, "#5f6368").ifPresent(elements::add);
return elements;
}
private List<SvgElement> renderLibreOfficeBanner(
String title,
double bannerHeight
) {
var elements = new ArrayList<SvgElement>();
// Title bar
elements.add(new SvgRect("0",
"0",
"100%",
"30",
attr("fill", "#dfdfdf")));
elements.add(new SvgText("10",
"20",
"12",
"black",
title + " - LibreOffice Writer",
attr("font-family", "Arial")));
// Menu bar
elements.add(new SvgRect("0",
"30",
"100%",
"25",
attr("fill", "#eeeeee")));
elements.add(new SvgText("10",
"47",
"11",
"black",
"File Edit View Insert Format Styles Table Form Tools"
+ " Window Help",
attr("font-family", "Arial")));
// Standard toolbar row
elements.add(new SvgRect("0",
"55",
"100%",
"22",
attr("fill", "#eeeeee")));
elements.add(new SvgLine("0", "55", "100%", "55", "#ccc"));
// File operations
findIcon(NEW, 10, 58, 16, "#333").ifPresent(elements::add);
findIcon(OPEN, 35, 58, 16, "#333").ifPresent(elements::add);
findIcon(SAVE, 60, 58, 16, "#333").ifPresent(elements::add);
findIcon(PDF_EXPORT, 85, 58, 16, "#333").ifPresent(elements::add);
findIcon(PRINT, 110, 58, 16, "#333").ifPresent(elements::add);
// Clipboard operations
findIcon(CUT, 145, 58, 14, "#333").ifPresent(elements::add);
findIcon(COPY, 165, 58, 14, "#333").ifPresent(elements::add);
findIcon(PASTE, 185, 58, 14, "#333").ifPresent(elements::add);
findIcon(FORMAT_PAINTER, 210, 58, 14, "#333").ifPresent(elements::add);
// Undo / Redo / Search
findIcon(UNDO, 240, 58, 16, "#333").ifPresent(elements::add);
findIcon(REDO, 265, 58, 16, "#333").ifPresent(elements::add);
findIcon(SEARCH, 295, 58, 14, "#333").ifPresent(elements::add);
findIcon(SPELLCHECK, 315, 58, 14, "#333").ifPresent(elements::add);
// Insert objects
findIcon(TABLE, 350, 58, 14, "#333").ifPresent(elements::add);
findIcon(IMAGE, 370, 58, 14, "#333").ifPresent(elements::add);
findIcon(CHART, 390, 58, 14, "#333").ifPresent(elements::add);
findIcon(LINK, 410, 58, 14, "#333").ifPresent(elements::add);
findIcon(COMMENT, 430, 58, 14, "#333").ifPresent(elements::add);
// Formatting toolbar row
elements.add(new SvgRect("0",
"77",
"100%",
String.valueOf(bannerHeight - 77),
attr("fill", "#eeeeee")));
elements.add(new SvgLine("0", "77", "100%", "77", "#ccc"));
elements.add(new SvgLine("0", "100", "100%", "100", "#ccc"));
// Text formatting
findIcon(BOLD, 10, 80, 16, "#333").ifPresent(elements::add);
findIcon(ITALIC, 35, 80, 16, "#333").ifPresent(elements::add);
findIcon(UNDERLINE, 60, 80, 16, "#333").ifPresent(elements::add);
findIcon(STRIKETHROUGH, 85, 80, 14, "#333").ifPresent(elements::add);
findIcon(SUBSCRIPT, 105, 80, 14, "#333").ifPresent(elements::add);
findIcon(SUPERSCRIPT, 125, 80, 14, "#333").ifPresent(elements::add);
findIcon(FONT_COLOR, 150, 80, 14, "#333").ifPresent(elements::add);
findIcon(HIGHLIGHT, 170, 80, 14, "#333").ifPresent(elements::add);
// Alignment
findIcon(ALIGN_LEFT, 205, 80, 14, "#333").ifPresent(elements::add);
findIcon(ALIGN_CENTER, 225, 80, 14, "#333").ifPresent(elements::add);
findIcon(ALIGN_RIGHT, 245, 80, 14, "#333").ifPresent(elements::add);
findIcon(JUSTIFY, 265, 80, 14, "#333").ifPresent(elements::add);
// Lists
findIcon(BULLETS, 295, 80, 14, "#333").ifPresent(elements::add);
findIcon(NUMBERING, 315, 80, 14, "#333").ifPresent(elements::add);
findIcon(INDENT_DECREASE, 340, 80, 14, "#333").ifPresent(elements::add);
findIcon(INDENT_INCREASE, 360, 80, 14, "#333").ifPresent(elements::add);
// Paragraph marks
findIcon(PARAGRAPH_MARKS, 390, 80, 14, "#333").ifPresent(elements::add);
return elements;
}
}