From c9e721132366351c851a7f4be0cd88305d84f887 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sat, 7 Nov 2020 15:05:16 +0100 Subject: [PATCH] Added more to the bookbuilder, not done yet though. --- pom.xml | 7 ++ .../blockbookbinder/BlockBookBinder.java | 3 + .../control/BookExportActionListener.java | 41 +++++++++++ .../control/BookPagePasteListener.java | 51 +++++++++++++ .../control/ConvertToBookActionListener.java | 23 ++++++ .../blockbookbinder/model/Book.java | 10 +++ .../blockbookbinder/model/BookBuilder.java | 73 +++++++++++++++++++ .../blockbookbinder/model/BookPage.java | 29 ++++++-- .../model/CharWidthMapper.java | 56 ++++++++++++++ .../util/ApplicationProperties.java | 14 ++++ .../view/BookPreviewPanel.java | 67 +++++++++++++++-- .../blockbookbinder/view/MainFrame.java | 13 ++-- .../blockbookbinder/view/SourceTextPanel.java | 20 ++++- src/main/resources/application.properties | 2 + 14 files changed, 388 insertions(+), 21 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java create mode 100644 src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java create mode 100644 src/main/java/nl/andrewlalis/blockbookbinder/control/ConvertToBookActionListener.java create mode 100644 src/main/java/nl/andrewlalis/blockbookbinder/model/BookBuilder.java create mode 100644 src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java diff --git a/pom.xml b/pom.xml index 0d82aea..c2852c8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,5 +21,12 @@ 1.18.14 provided + + + + com.1stleg + jnativehook + 2.1.0 + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/BlockBookBinder.java b/src/main/java/nl/andrewlalis/blockbookbinder/BlockBookBinder.java index 80f0b23..3c5ca22 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/BlockBookBinder.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/BlockBookBinder.java @@ -1,8 +1,11 @@ package nl.andrewlalis.blockbookbinder; import nl.andrewlalis.blockbookbinder.view.MainFrame; +import org.jnativehook.GlobalScreen; import javax.swing.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * The main class for the application. diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java new file mode 100644 index 0000000..2a2266f --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookExportActionListener.java @@ -0,0 +1,41 @@ +package nl.andrewlalis.blockbookbinder.control; + +import nl.andrewlalis.blockbookbinder.model.Book; +import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel; +import org.jnativehook.GlobalScreen; +import org.jnativehook.NativeHookException; + +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BookExportActionListener implements ActionListener { + private final BookPreviewPanel bookPreviewPanel; + private final Clipboard clipboard; + + public BookExportActionListener(BookPreviewPanel bookPreviewPanel) { + this.bookPreviewPanel = bookPreviewPanel; + this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Starting export."); + final Book book = this.bookPreviewPanel.getBook(); + BookPagePasteListener pasteListener = new BookPagePasteListener(book, clipboard); + try { + // For catching native events, set logging here. + Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName()); + logger.setLevel(Level.WARNING); + logger.setUseParentHandlers(false); + GlobalScreen.registerNativeHook(); + GlobalScreen.addNativeKeyListener(pasteListener); + } catch (NativeHookException nativeHookException) { + System.err.println("Could not register native hook."); + nativeHookException.printStackTrace(); + } + } +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java new file mode 100644 index 0000000..8a2fcec --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/BookPagePasteListener.java @@ -0,0 +1,51 @@ +package nl.andrewlalis.blockbookbinder.control; + +import nl.andrewlalis.blockbookbinder.model.Book; +import org.jnativehook.GlobalScreen; +import org.jnativehook.NativeHookException; +import org.jnativehook.keyboard.NativeKeyEvent; +import org.jnativehook.keyboard.NativeKeyListener; + +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; + +public class BookPagePasteListener implements NativeKeyListener { + private final Book book; + private final Clipboard clipboard; + private int nextPage; + + public BookPagePasteListener(Book book, Clipboard clipboard) { + this.book = book; + this.clipboard = clipboard; + this.nextPage = 0; + } + + @Override + public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {} + + @Override + public void nativeKeyPressed(NativeKeyEvent nativeKeyEvent) { + if (nativeKeyEvent.getKeyCode() == NativeKeyEvent.VC_V && (nativeKeyEvent.getModifiers() & NativeKeyEvent.CTRL_MASK) > 0) { + System.out.println("CTRL + V -> Paste!!!"); + clipboard.setContents( + new StringSelection(book.getPages().get(this.nextPage).toString()), + null + ); + this.nextPage++; + System.out.println("Incremented page."); + if (this.nextPage >= this.book.getPageCount()) { + try { + GlobalScreen.removeNativeKeyListener(this); + GlobalScreen.unregisterNativeHook(); + System.out.println("Done pasting."); + } catch (NativeHookException nativeHookException) { + System.err.println("Could not unregister a native hook."); + nativeHookException.printStackTrace(); + } + } + } + } + + @Override + public void nativeKeyReleased(NativeKeyEvent nativeKeyEvent) {} +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/ConvertToBookActionListener.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/ConvertToBookActionListener.java new file mode 100644 index 0000000..90343aa --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/ConvertToBookActionListener.java @@ -0,0 +1,23 @@ +package nl.andrewlalis.blockbookbinder.control; + +import nl.andrewlalis.blockbookbinder.model.BookBuilder; +import nl.andrewlalis.blockbookbinder.view.BookPreviewPanel; +import nl.andrewlalis.blockbookbinder.view.SourceTextPanel; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ConvertToBookActionListener implements ActionListener { + private final SourceTextPanel sourceTextPanel; + private final BookPreviewPanel bookPreviewPanel; + + public ConvertToBookActionListener(SourceTextPanel sourceTextPanel, BookPreviewPanel bookPreviewPanel) { + this.sourceTextPanel = sourceTextPanel; + this.bookPreviewPanel = bookPreviewPanel; + } + + @Override + public void actionPerformed(ActionEvent e) { + this.bookPreviewPanel.setBook(new BookBuilder().build(this.sourceTextPanel.getSourceText())); + } +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/Book.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/Book.java index 1c3ed61..8d80596 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/model/Book.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/Book.java @@ -16,4 +16,14 @@ public class Book { public int getPageCount() { return this.pages.size(); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Book of " + this.getPageCount() + " pages:\n"); + for (int i = 0; i < this.getPageCount(); i++) { + BookPage page = this.pages.get(i); + sb.append("Page ").append(i + 1).append(":\n").append(page).append('\n'); + } + return sb.toString(); + } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/BookBuilder.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/BookBuilder.java new file mode 100644 index 0000000..2cadc8f --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/BookBuilder.java @@ -0,0 +1,73 @@ +package nl.andrewlalis.blockbookbinder.model; + +import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; + +/** + * Class which helps construct formatted book pages from a source text. + */ +public class BookBuilder { + public Book build(String source) { + Book book = new Book(); + char[] sourceChars = source.trim().toCharArray(); + + final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines"); + final int maxLineWidth = ApplicationProperties.getIntProp("book.page_max_width"); + final CharWidthMapper charWidthMapper = new CharWidthMapper(); + + BookPage currentPage = new BookPage(); + StringBuilder lineStringBuilder = new StringBuilder(64); + int pageLineCount = 1; // Current line on the page we're on. + int lineCharWidth = 0; // Total pixel width of the current line so far. + int i = 0; + while (i < sourceChars.length) { + final char c = sourceChars[i]; + if (c == '\n') { + i++; + continue; + } + final int cWidth = charWidthMapper.getWidth(c); + boolean newLineNeeded = lineCharWidth + cWidth + 1 > maxLineWidth; + boolean newPageNeeded = pageLineCount == maxLines && newLineNeeded; + System.out.println("Current char: " + c + ", Current Line: " + pageLineCount + ", Current Line Char Width: " + lineCharWidth + ", New line needed: " + newLineNeeded + ", New page needed: " + newPageNeeded); + + // Check if the page is full, and append it to the book, and refresh. + if (newPageNeeded) { + // If necessary, append whatever is left in the last line to the page. + if (lineStringBuilder.length() > 0) { + currentPage.addLine(lineStringBuilder.toString()); + } + book.getPages().add(currentPage); + currentPage = new BookPage(); + // Reset all buffers and counters for the next page. + lineStringBuilder.setLength(0); + newLineNeeded = false; + pageLineCount = 1; + lineCharWidth = 0; + } + + // Check if the line is full, and append it to the page and refresh. + if (newLineNeeded) { + currentPage.addLine(lineStringBuilder.toString()); + // Reset line status info. + lineStringBuilder.setLength(0); + pageLineCount++; + lineCharWidth = 0; + } + + // Finally, append the char to the current line. + lineStringBuilder.append(c); + lineCharWidth += cWidth + 1; + i++; + } + + // Append a final page with the remainder of the text. + if (currentPage.hasContent()) { + if (lineStringBuilder.length() > 0) { + currentPage.addLine(lineStringBuilder.toString()); + } + book.getPages().add(currentPage); + } + + return book; + } +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/BookPage.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/BookPage.java index f05f87a..17f903f 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/model/BookPage.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/BookPage.java @@ -1,14 +1,31 @@ package nl.andrewlalis.blockbookbinder.model; -import lombok.Getter; -import lombok.Setter; +import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; + +import java.util.ArrayList; +import java.util.List; public class BookPage { - @Getter - @Setter - private String content; + private final List lines; public BookPage() { - this.content = ""; + this.lines = new ArrayList<>(ApplicationProperties.getIntProp("book.page_max_lines")); + } + + public boolean addLine(String line) { + if (this.lines.size() == ApplicationProperties.getIntProp("book.page_max_lines")) { + return false; + } + this.lines.add(line); + return true; + } + + public boolean hasContent() { + return !this.lines.isEmpty(); + } + + @Override + public String toString() { + return String.join("\n", this.lines); } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java new file mode 100644 index 0000000..21fe21d --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java @@ -0,0 +1,56 @@ +package nl.andrewlalis.blockbookbinder.model; + +import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; + +import java.util.HashMap; +import java.util.Map; + +public class CharWidthMapper { + private final Map charWidthMap; + + public CharWidthMapper() { + this.charWidthMap = new HashMap<>(); + this.initCharWidthMap(); + } + + public int getWidth(char c) { + return this.charWidthMap.getOrDefault(c, 0); + } + + private void initCharWidthMap() { + this.charWidthMap.put(' ', 3); + this.charWidthMap.put('!', 1); + this.charWidthMap.put('"', 3); + this.charWidthMap.put('\'', 1); + this.charWidthMap.put('(', 3); + this.charWidthMap.put(')', 3); + this.charWidthMap.put('*', 3); + this.charWidthMap.put(',', 1); + this.charWidthMap.put('.', 1); + this.charWidthMap.put(':', 1); + this.charWidthMap.put(';', 1); + this.charWidthMap.put('<', 4); + this.charWidthMap.put('>', 4); + this.charWidthMap.put('@', 6); + this.charWidthMap.put('I', 3); + this.charWidthMap.put('[', 3); + this.charWidthMap.put(']', 3); + this.charWidthMap.put('`', 2); + this.charWidthMap.put('f', 4); + this.charWidthMap.put('i', 1); + this.charWidthMap.put('k', 4); + this.charWidthMap.put('l', 2); + this.charWidthMap.put('t', 3); + this.charWidthMap.put('{', 3); + this.charWidthMap.put('|', 1); + this.charWidthMap.put('}', 3); + this.charWidthMap.put('~', 6); + + final int defaultWidth = ApplicationProperties.getIntProp("book.default_char_width"); + for (char c = 32; c < 127; c++) { + if (!this.charWidthMap.containsKey(c)) { + this.charWidthMap.put(c, defaultWidth); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/util/ApplicationProperties.java b/src/main/java/nl/andrewlalis/blockbookbinder/util/ApplicationProperties.java index 6e8dc0b..63ad45c 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/util/ApplicationProperties.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/util/ApplicationProperties.java @@ -3,6 +3,8 @@ package nl.andrewlalis.blockbookbinder.util; import lombok.Getter; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; /** @@ -14,6 +16,8 @@ public class ApplicationProperties { @Getter private final Properties properties; + private final Map intPropCache; + public static ApplicationProperties getInstance() { if (instance == null) { try { @@ -35,8 +39,18 @@ public class ApplicationProperties { return getInstance().getProperties().getProperty(key); } + public static Integer getIntProp(String key) { + Integer value = getInstance().intPropCache.get(key); + if (value == null) { + value = Integer.parseInt(getProp(key)); + getInstance().intPropCache.put(key, value); + } + return value; + } + private ApplicationProperties() throws IOException { this.properties = new Properties(); this.properties.load(this.getClass().getClassLoader().getResourceAsStream("application.properties")); + this.intPropCache = new HashMap<>(); } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/BookPreviewPanel.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/BookPreviewPanel.java index 665621f..ca1c237 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/view/BookPreviewPanel.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/BookPreviewPanel.java @@ -1,30 +1,87 @@ package nl.andrewlalis.blockbookbinder.view; +import lombok.Getter; import nl.andrewlalis.blockbookbinder.model.Book; +import nl.andrewlalis.blockbookbinder.model.BookPage; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; +import java.io.IOException; +import java.io.InputStream; /** * A customized panel that's dedicated to showing a book's contents. */ public class BookPreviewPanel extends JPanel { + @Getter private Book book; + private int currentPage = 0; + private final JTextArea previewPageTextArea; + private final JLabel titleLabel; public BookPreviewPanel() { super(new BorderLayout()); - this.add(new JLabel("Book Preview"), BorderLayout.NORTH); + this.titleLabel = new JLabel("Book Preview"); + this.add(this.titleLabel, BorderLayout.NORTH); this.setBorder(new EmptyBorder(5, 5, 5, 5)); - JTextArea previewPageTextArea = new JTextArea(); - JScrollPane previewPageScrollPane = new JScrollPane(previewPageTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + this.previewPageTextArea = new JTextArea(); + this.previewPageTextArea.setEditable(false); + try { + InputStream is = this.getClass().getClassLoader().getResourceAsStream("fonts/1_Minecraft-Regular.otf"); + if (is == null) { + throw new IOException("Could not read minecraft font."); + } + Font mcFont = Font.createFont(Font.TRUETYPE_FONT, is); + mcFont = mcFont.deriveFont(24.0f); + this.previewPageTextArea.setFont(mcFont); + } catch (FontFormatException | IOException e) { + e.printStackTrace(); + } + JScrollPane previewPageScrollPane = new JScrollPane(this.previewPageTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); this.add(previewPageScrollPane, BorderLayout.CENTER); JPanel previewButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - previewButtonPanel.add(new JButton("<")); - previewButtonPanel.add(new JButton(">")); + JButton previousPageButton = new JButton("Previous Page"); + previousPageButton.addActionListener(e -> { + if (currentPage > 0) { + currentPage--; + displayCurrentPage(); + } + }); + JButton nextPageButton = new JButton("Next Page"); + nextPageButton.addActionListener(e -> { + if (currentPage < book.getPageCount() - 1) { + currentPage++; + displayCurrentPage(); + } + }); + previewButtonPanel.add(previousPageButton); + previewButtonPanel.add(nextPageButton); this.add(previewButtonPanel, BorderLayout.SOUTH); + + this.setBook(new Book()); + } + + private void displayCurrentPage() { + if (this.book.getPageCount() == 0) { + return; + } + BookPage currentPage = this.book.getPages().get(this.currentPage); + this.previewPageTextArea.setText(currentPage.toString()); + this.titleLabel.setText("Book Preview (Page " + (this.currentPage + 1) + " of " + this.book.getPageCount() + ")"); + } + + public void setBook(Book book) { + this.book = book; + this.currentPage = 0; + this.displayCurrentPage(); + } + + public void setCurrentPage(int page) { + this.currentPage = page; + this.displayCurrentPage(); } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java index 16b7060..cd591ee 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java @@ -1,5 +1,6 @@ package nl.andrewlalis.blockbookbinder.view; +import nl.andrewlalis.blockbookbinder.control.BookExportActionListener; import nl.andrewlalis.blockbookbinder.control.ImportAction; import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; @@ -56,17 +57,15 @@ public class MainFrame extends JFrame { JPanel mainPanel = new JPanel(new BorderLayout()); JPanel doublePanel = new JPanel(new GridLayout(1, 2)); - doublePanel.add(new BookPreviewPanel()); - doublePanel.add(new SourceTextPanel()); + BookPreviewPanel bookPreviewPanel = new BookPreviewPanel(); + doublePanel.add(bookPreviewPanel); + SourceTextPanel sourceTextPanel = new SourceTextPanel(bookPreviewPanel); + doublePanel.add(sourceTextPanel); mainPanel.add(doublePanel, BorderLayout.CENTER); JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JButton exportButton = new JButton("Export to Book"); - exportButton.addActionListener(e -> { - System.out.println("Starting export."); -// final String fullText = mainTextArea.getText().trim(); -// Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fullText.substring(0, 200)), null); - }); + exportButton.addActionListener(new BookExportActionListener(bookPreviewPanel)); bottomPanel.add(exportButton); mainPanel.add(bottomPanel, BorderLayout.SOUTH); diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/SourceTextPanel.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/SourceTextPanel.java index df006ae..95ff0a8 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/view/SourceTextPanel.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/SourceTextPanel.java @@ -1,5 +1,7 @@ package nl.andrewlalis.blockbookbinder.view; +import nl.andrewlalis.blockbookbinder.control.ConvertToBookActionListener; + import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; @@ -9,14 +11,18 @@ import java.awt.*; * a book. */ public class SourceTextPanel extends JPanel { - public SourceTextPanel() { + private final JTextArea textArea; + + public SourceTextPanel(BookPreviewPanel bookPreviewPanel) { super(new BorderLayout()); this.add(new JLabel("Source Text"), BorderLayout.NORTH); this.setBorder(new EmptyBorder(5, 5, 5, 5)); - JTextArea mainTextArea = new JTextArea(); - JScrollPane scrollWrappedMainTextArea = new JScrollPane(mainTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + this.textArea = new JTextArea(); + this.textArea.setWrapStyleWord(true); + this.textArea.setLineWrap(true); + JScrollPane scrollWrappedMainTextArea = new JScrollPane(this.textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); this.add(scrollWrappedMainTextArea, BorderLayout.CENTER); JPanel rightPanelButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); @@ -24,5 +30,13 @@ public class SourceTextPanel extends JPanel { JButton importButton = new JButton("Import"); importButton.setActionCommand("importSource"); rightPanelButtonPanel.add(importButton); + + JButton convertButton = new JButton("Convert to Book"); + convertButton.addActionListener(new ConvertToBookActionListener(this, bookPreviewPanel)); + rightPanelButtonPanel.add(convertButton); + } + + public String getSourceText() { + return this.textArea.getText(); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c28913c..fd49145 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,3 +7,5 @@ frame.default_height=600 book.max_pages=100 book.page_max_lines=14 book.page_max_width=113 +book.page_max_chars=255 +book.default_char_width=5