diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction.java index 3587b78..ed8a75d 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction.java @@ -3,6 +3,8 @@ package nl.andrewlalis.blockbookbinder.control.source; import lombok.Getter; import lombok.Setter; import nl.andrewlalis.blockbookbinder.model.build.BookBuilder; +import nl.andrewlalis.blockbookbinder.model.build.BookBuilder2; +import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; import nl.andrewlalis.blockbookbinder.view.SourceTextPanel; import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel; diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction2.java b/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction2.java new file mode 100644 index 0000000..c30f2b5 --- /dev/null +++ b/src/main/java/nl/andrewlalis/blockbookbinder/control/source/CompileFromSourceAction2.java @@ -0,0 +1,37 @@ +package nl.andrewlalis.blockbookbinder.control.source; + +import lombok.Getter; +import lombok.Setter; +import nl.andrewlalis.blockbookbinder.model.build.BookBuilder2; +import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; +import nl.andrewlalis.blockbookbinder.view.SourceTextPanel; +import nl.andrewlalis.blockbookbinder.view.book.BookPreviewPanel; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class CompileFromSourceAction2 extends AbstractAction { + @Getter + private static final CompileFromSourceAction2 instance = new CompileFromSourceAction2(); + + @Setter + private SourceTextPanel sourceTextPanel; + @Setter + private BookPreviewPanel bookPreviewPanel; + + public CompileFromSourceAction2() { + super("Compile From Source 2"); + this.putValue(SHORT_DESCRIPTION, "Compile the current source text into a book."); + } + + @Override + public void actionPerformed(ActionEvent e) { + this.bookPreviewPanel.setBook( + new BookBuilder2( + ApplicationProperties.getIntProp("book.page_max_lines"), + ApplicationProperties.getIntProp("book.page_max_chars"), + ApplicationProperties.getIntProp("book.page_max_width") + ).addText(this.sourceTextPanel.getSourceText()).build() + ); + } +} diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java index ad5351b..cf9a700 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/CharWidthMapper.java @@ -17,8 +17,20 @@ public class CharWidthMapper { this.initCharWidthMap(); } - public int getWidth(char c) { - return this.charWidthMap.getOrDefault(c, 6); + public static int getWidth(char c) { + return instance.charWidthMap.getOrDefault(c, 6); + } + + public static int getWidth(String s) { + if (s.length() == 0) return 0; + int width = 0; + for (int i = 0; i < s.length(); i++) { + width += getWidth(s.charAt(i)); + if (i < s.length() - 1) { + width++; + } + } + return width; } private void initCharWidthMap() { diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder.java index c9bd747..9c9ea6d 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder.java @@ -58,7 +58,7 @@ public class BookBuilder { sourceIndex++; symbolBuilder.setLength(0); symbolBuilder.append(c); - int symbolWidth = CharWidthMapper.getInstance().getWidth(c); + int symbolWidth = CharWidthMapper.getWidth(c); // Since there's a 1-pixel gap between characters, add it to the width if this isn't the first char. if (lineBuilder.length() > 0) { @@ -86,7 +86,7 @@ public class BookBuilder { ) { char nextChar = sourceChars[sourceIndex]; symbolBuilder.append(nextChar); - symbolWidth += 1 + CharWidthMapper.getInstance().getWidth(nextChar); + symbolWidth += 1 + CharWidthMapper.getWidth(nextChar); sourceIndex++; } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder2.java b/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder2.java index ce0e87c..5c17219 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder2.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder2.java @@ -12,13 +12,10 @@ public class BookBuilder2 { private final int MAX_CHARS_PER_PAGE; private final int MAX_LINE_PIXEL_WIDTH; - private List lines; + private final List lines; - private StringBuilder lineBuilder; - private StringBuilder wordBuilder; - private int currentLine; - private int currentLinePixelWidth; - private int currentWordPixelWidth; + private final StringBuilder lineBuilder; + private final StringBuilder wordBuilder; public BookBuilder2(int maxLinesPerPage, int maxCharsPerPage, int maxLinePixelWidth) { this.MAX_LINES_PER_PAGE = maxLinesPerPage; @@ -27,9 +24,6 @@ public class BookBuilder2 { this.lines = new ArrayList<>(); this.lineBuilder = new StringBuilder(64); this.wordBuilder = new StringBuilder(64); - this.currentLine = 0; - this.currentLinePixelWidth = 0; - this.currentWordPixelWidth = 0; } public BookBuilder2 addText(String text) { @@ -40,9 +34,37 @@ public class BookBuilder2 { appendLine(); } else if (c == ' ' && lineBuilder.length() == 0) { continue; // Skip spaces at the start of lines. + } else if (Character.isWhitespace(c)) { + if (CharWidthMapper.getWidth(lineBuilder.toString() + c) > MAX_LINE_PIXEL_WIDTH) { + appendLine(); + if (c != ' ') { + lineBuilder.append(c); + } + } else { + lineBuilder.append(c); + } } else { // Read a continuous word. - int charsRead = readWord(text, idx - 1); - idx += charsRead - 1; + String word = readWord(text, idx - 1); + idx += word.length() - 1; + if (CharWidthMapper.getWidth(lineBuilder + word) <= MAX_LINE_PIXEL_WIDTH) { + // Append the word if it'll fit completely. + lineBuilder.append(word); + } else if (CharWidthMapper.getWidth(word) <= MAX_LINE_PIXEL_WIDTH) { + // Go to the next line and put the word there, since it'll fit. + appendLine(); + lineBuilder.append(word); + } else { + // The word is so large that it doesn't fit on a line on its own. + // Find the largest substring of the word that'll fit with a hyphen. + int subStringSize = word.length() - 2; + while (CharWidthMapper.getWidth(word.substring(0, subStringSize) + "-") > MAX_LINE_PIXEL_WIDTH) { + subStringSize--; + } + appendLine(); + lineBuilder.append(word, 0, subStringSize).append('-'); + appendLine(); + lineBuilder.append(word.substring(subStringSize)); + } } } return this; @@ -52,14 +74,28 @@ public class BookBuilder2 { Book book = new Book(); BookPage page = new BookPage(); int currentPageLineCount = 0; + int currentPageCharCount = 0; + + // Flush anything remaining in lineBuilder to a final line. + if (lineBuilder.length() > 0) { + appendLine(); + } for (String line : lines) { + if (currentPageCharCount + line.length() > MAX_CHARS_PER_PAGE) { + book.addPage(page); + page = new BookPage(); + currentPageLineCount = 0; + currentPageCharCount = 0; + } page.addLine(line); currentPageLineCount++; + currentPageCharCount += line.length(); if (currentPageLineCount == MAX_LINES_PER_PAGE) { book.addPage(page); page = new BookPage(); currentPageLineCount = 0; + currentPageCharCount = 0; } } if (page.hasContent()) { @@ -68,32 +104,22 @@ public class BookBuilder2 { return book; } - private int readWord(String text, int firstCharIdx) { - currentWordPixelWidth = 0; + private String readWord(String text, int firstCharIdx) { wordBuilder.setLength(0); int idx = firstCharIdx; while (idx < text.length()) { char c = text.charAt(idx++); if (!Character.isWhitespace(c)) { - currentWordPixelWidth += CharWidthMapper.getInstance().getWidth(c) + 1; wordBuilder.append(c); - - // If we notice that our word will cause the current line to exceed max width, go to a newline. - if (currentLinePixelWidth + currentWordPixelWidth > MAX_LINE_PIXEL_WIDTH) { - appendLine(); - } } else { break; } } - String word = wordBuilder.toString(); - return word.length(); + return wordBuilder.toString(); } private void appendLine() { this.lines.add(this.lineBuilder.toString()); this.lineBuilder.setLength(0); - this.currentLine++; - this.currentLinePixelWidth = 0; } } diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java index 28f763d..66f2748 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/MainFrame.java @@ -4,6 +4,7 @@ import nl.andrewlalis.blockbookbinder.BlockBookBinder; import nl.andrewlalis.blockbookbinder.control.export.ExportBookToMinecraftAction; import nl.andrewlalis.blockbookbinder.control.source.CleanSourceAction; import nl.andrewlalis.blockbookbinder.control.source.CompileFromSourceAction; +import nl.andrewlalis.blockbookbinder.control.source.CompileFromSourceAction2; import nl.andrewlalis.blockbookbinder.control.source.ImportSourceAction; import nl.andrewlalis.blockbookbinder.util.ApplicationProperties; import nl.andrewlalis.blockbookbinder.view.about.AboutDialog; @@ -44,11 +45,13 @@ public class MainFrame extends JFrame { BookPreviewPanel bookPreviewPanel = new BookPreviewPanel(); doublePanel.add(bookPreviewPanel); CompileFromSourceAction.getInstance().setBookPreviewPanel(bookPreviewPanel); + CompileFromSourceAction2.getInstance().setBookPreviewPanel(bookPreviewPanel); ExportBookToMinecraftAction.getInstance().setBookPreviewPanel(bookPreviewPanel); SourceTextPanel sourceTextPanel = new SourceTextPanel(); doublePanel.add(sourceTextPanel); CompileFromSourceAction.getInstance().setSourceTextPanel(sourceTextPanel); + CompileFromSourceAction2.getInstance().setSourceTextPanel(sourceTextPanel); CleanSourceAction.getInstance().setSourceTextPanel(sourceTextPanel); mainPanel.add(doublePanel, BorderLayout.CENTER); @@ -68,6 +71,7 @@ public class MainFrame extends JFrame { JMenu bookMenu = new JMenu("Book"); bookMenu.add(CompileFromSourceAction.getInstance()); + bookMenu.add(CompileFromSourceAction2.getInstance()); bookMenu.add(CleanSourceAction.getInstance()); bookMenu.add(ExportBookToMinecraftAction.getInstance()); menuBar.add(bookMenu); diff --git a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java index 4d1d42f..ee8571d 100644 --- a/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java +++ b/src/main/java/nl/andrewlalis/blockbookbinder/view/export/ExportToBookDialog.java @@ -51,25 +51,32 @@ public class ExportToBookDialog extends JDialog { private Container buildContentPane() { JPanel mainPanel = new JPanel(new BorderLayout()); - JPanel setupPanel = new JPanel(); - setupPanel.setLayout(new BoxLayout(setupPanel, BoxLayout.PAGE_AXIS)); + JPanel setupPanel = new JPanel(new GridBagLayout()); + String[] labels = {"", "First Page", "Last Page", "Auto-Paste Delay (Seconds)"}; this.autoCheckbox = new JCheckBox("Auto-paste", true); this.firstPageSpinner = new JSpinner(new SpinnerNumberModel(1, 1, this.book.getPageCount(), 1)); - JPanel firstPageSpinnerPanel = new JPanel(new BorderLayout()); - firstPageSpinnerPanel.add(new JLabel("First Page:"), BorderLayout.WEST); - firstPageSpinnerPanel.add(this.firstPageSpinner, BorderLayout.CENTER); this.lastPageSpinner = new JSpinner(new SpinnerNumberModel(this.book.getPageCount(), 1, this.book.getPageCount(), 1)); - JPanel lastPageSpinnerPanel = new JPanel(new BorderLayout()); - lastPageSpinnerPanel.add(new JLabel("Last Page:"), BorderLayout.WEST); - lastPageSpinnerPanel.add(this.lastPageSpinner, BorderLayout.CENTER); this.autoPasteDelaySpinner = new JSpinner(new SpinnerNumberModel(0.2, 0.1, 5.0, 0.1)); - JPanel autoPasteDelaySpinnerPanel = new JPanel(new BorderLayout()); - autoPasteDelaySpinnerPanel.add(new JLabel("Auto-Paste Delay (s):"), BorderLayout.WEST); - autoPasteDelaySpinnerPanel.add(this.autoPasteDelaySpinner, BorderLayout.CENTER); - setupPanel.add(this.autoCheckbox); - setupPanel.add(firstPageSpinnerPanel); - setupPanel.add(lastPageSpinnerPanel); - setupPanel.add(autoPasteDelaySpinnerPanel); + + JComponent[] fields = {autoCheckbox, firstPageSpinner, lastPageSpinner, autoPasteDelaySpinner}; + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.LINE_START; + c.weightx = 0.5; + c.fill = GridBagConstraints.NONE; + c.insets = new Insets(5, 5, 5, 5); + for (String label : labels) { + setupPanel.add(new JLabel(label), c); + c.gridy++; + } + c.gridx = 1; + c.gridy = 0; + c.fill = GridBagConstraints.HORIZONTAL; + for (JComponent field : fields) { + setupPanel.add(field, c); + c.gridy++; + } this.exportStatusPanel = new ExportStatusPanel(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 87e1343..af77fad 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ -version=1.2.0 +version=1.3.0 # Settings for the application's GUI. frame.title=Block Book Binder @@ -19,5 +19,5 @@ about_dialog.source=html/about.html book.max_pages=100 book.page_max_lines=14 book.page_max_width=113 -book.page_max_chars=255 +book.page_max_chars=700 book.default_char_width=5