Skip to content

Commit

Permalink
#256 interim work on font creator tool
Browse files Browse the repository at this point in the history
  • Loading branch information
davetcc committed Nov 19, 2022
1 parent 36b9817 commit f132374
Show file tree
Hide file tree
Showing 20 changed files with 1,300 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package com.thecoderscorner.menu.editorui.controller;

import com.thecoderscorner.menu.editorui.dialog.SelectUnicodeRangesDialog;
import com.thecoderscorner.menu.editorui.generator.core.VariableNameGenerator;
import com.thecoderscorner.menu.editorui.generator.font.AwtLoadedFont;
import com.thecoderscorner.menu.editorui.generator.font.LoadedFont;
import com.thecoderscorner.menu.editorui.generator.font.NoLoadedFont;
import com.thecoderscorner.menu.editorui.generator.font.UnicodeBlockMapping;
import com.thecoderscorner.menu.editorui.uimodel.CurrentProjectEditorUI;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import javax.swing.*;
import java.awt.*;
import java.nio.file.Path;
import java.util.*;
import java.util.List;

import static com.thecoderscorner.menu.editorui.generator.font.AwtLoadedFont.*;

public class CreateFontUtilityController {
public TextField fontFileField;
public Button onHelp;
public Button onToggleSelected;
public Button onGenerateAdafruit;
public Button onGenerateTcUnicode;
public Label approxSizeField;
public Spinner<Integer> pixelSizeSpinner;
public ComboBox<FontStyle> fontStyleCombo;
public TextField outputStructNameField;
public GridPane fontRenderArea;
public Menu loadedFontsMenu;
private CurrentProjectEditorUI editorUI;
private String homeDirectory;
private Path currentDir;
private LoadedFont loadedFont = NO_LOADED_FONT;
private Set<UnicodeBlockMapping> blockMappings = Set.of();
private Map<UnicodeBlockMapping,List<ToggleButton>> controlsByBlock = new HashMap<>();
private Map<Integer, Boolean> currentlySelected = new HashMap<>();

public void initialise(CurrentProjectEditorUI editorUI, String homeDirectory) {
this.editorUI = editorUI;
this.homeDirectory = homeDirectory;
var fileName = editorUI.getCurrentProject().getFileName();
currentDir = (fileName.equals("New")) ? Path.of(homeDirectory) : Path.of(fileName).getParent();
blockMappings = Set.of(UnicodeBlockMapping.BASIC_LATIN, UnicodeBlockMapping.LATIN1_SUPPLEMENT);

pixelSizeSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 255, 12));
pixelSizeSpinner.getValueFactory().valueProperty().addListener((observable, oldValue, newValue) -> {
changeNameField();
recalcFont();
});

this.fontStyleCombo.setItems(FXCollections.observableArrayList(FontStyle.values()));
this.fontStyleCombo.getSelectionModel().select(0);

SwingUtilities.invokeLater(() -> {
var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
for(var f : ge.getAllFonts()) {
MenuItem item = new MenuItem(f.getName() + " " + f.getFamily());
item.setOnAction(event -> {
fontFileField.setText("OS " + f.getName() + " " + f.getFamily());
loadedFont = new AwtLoadedFont(f, fontStyleCombo.getValue(), pixelSizeSpinner.getValue(), blockMappings);
});
loadedFontsMenu.getItems().add(item);
}
});
}

public void onChooseFont(ActionEvent actionEvent) {
var fileChoice = editorUI.findFileNameFromUser(Optional.of(currentDir), true, "Fonts|*.ttf");
fileChoice.ifPresent(file -> {
fontFileField.setText(file);
changeNameField();
loadedFont = new AwtLoadedFont(file, fontStyleCombo.getValue(), pixelSizeSpinner.getValue(), blockMappings);
recalcFont();
});
}

private void recalcFont() {
fontRenderArea.getChildren().clear();
controlsByBlock.clear();
loadedFont.deriveFont(fontStyleCombo.getValue(), pixelSizeSpinner.getValue());
int gridRow = 0;
for(var blockRange : UnicodeBlockMapping.values()) {
if(!blockMappings.contains(blockRange)) continue;

fontRenderArea.add(new Label(blockRange.toString()), 0, gridRow, 5, 1);
CheckBox selAllCheck = new CheckBox("Select/Clear All");
selAllCheck.setOnAction(event -> {
var allItems = controlsByBlock.get(blockRange);
if(allItems != null && !allItems.isEmpty()) {
for(var item : allItems) {
item.setSelected(selAllCheck.isSelected());
}
for(int i=blockRange.getStartingCode(); i<=blockRange.getEndingCode();i++) {
currentlySelected.put(i, selAllCheck.isSelected());
}
}
});
fontRenderArea.add(selAllCheck, 6, gridRow, 3, 1);

gridRow++;

var allButtons = new ArrayList<ToggleButton>();
int gridCol = 0;
for(int i=blockRange.getStartingCode(); i<blockRange.getEndingCode();i++) {
var maybeGlyph = loadedFont.getConvertedGlyph(i);
if (maybeGlyph.isPresent()) {
var glyph = maybeGlyph.get();
Image img = fromGlyphToImg(glyph);
var toggleButton = new ToggleButton("U" + glyph.code());
toggleButton.setGraphic(new ImageView(img));
toggleButton.setContentDisplay(ContentDisplay.TOP);
var selected = currentlySelected.get(glyph.code());
toggleButton.setSelected(selected != null && selected);
toggleButton.setOnAction(event -> currentlySelected.put(glyph.code(), toggleButton.isSelected()));
fontRenderArea.add(toggleButton, gridCol, gridRow);
allButtons.add(toggleButton);
GridPane.setMargin(toggleButton, new Insets(4));
gridCol++;
if(gridCol > 9) {
gridCol = 0;
gridRow++;
}
}
}
gridRow++;
controlsByBlock.put(blockRange, allButtons);
}
}

private Image fromGlyphToImg(ConvertedFontGlyph glyph) {
WritableImage img = new WritableImage(glyph.calculatedWidth() + 1, glyph.belowBaseline() + glyph.toBaseLine() + 1);
var writer = img.getPixelWriter();
int bitOffset = 0;
for(int y=glyph.fontDims().startY();y<glyph.fontDims().lastY(); y++) {
for(int x=glyph.fontDims().startX();x<glyph.fontDims().lastX(); x++) {
int byteOffset = bitOffset / 8;
if(byteOffset >= glyph.data().length) break;
int d = glyph.data()[byteOffset];
boolean on = (d & (1<<(bitOffset % 8))) != 0;
if(on) {
writer.setColor(x, y, Color.WHITE);
}
bitOffset++;
}
}
return img;
}

public void onFontStyleChanged(ActionEvent actionEvent) {
changeNameField();
recalcFont();
}

private void changeNameField() {
var file = Path.of(fontFileField.getText()).getFileName().toString();
file = file.replace(".ttf", "");
var outputName = file + " " + pixelSizeSpinner.getValue() + "pt " + fontStyleCombo.getValue();
outputStructNameField.setText(VariableNameGenerator.makeNameFromVariable(outputName));
}

public void onChooseUnicodeRanges(ActionEvent actionEvent) {
if(loadedFont instanceof NoLoadedFont) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Please select a font before choosing unicode ranges");
alert.setTitle("No font Selected");
alert.setHeaderText("No Font Selected");
alert.showAndWait();
} else {
Stage mainStage = (Stage) outputStructNameField.getScene().getWindow();
var dlg = new SelectUnicodeRangesDialog(mainStage, loadedFont, blockMappings);
dlg.getBlockMappings().ifPresent(unicodeBlockMappings -> {
blockMappings = unicodeBlockMappings;
recalcFont();
});

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,11 @@ public void onCodeShowLayout(ActionEvent actionEvent) {

public void onGenerateCode(ActionEvent event) {
try {
if(!editorProject.isFileNameSet()) {
editorUI.alertOnError("No filename set", "Please set a filename to continue");
return;
}

editorUI.showCodeGeneratorDialog(installer);
editorProject.saveProject(EditorSaveMode.SAVE);
redrawTreeControl();
Expand Down Expand Up @@ -659,6 +664,10 @@ public void onMenuInMenu(ActionEvent actionEvent) {
var menuInMenuDialog = new EditMenuInMenuDialog(getStage(), editorProject.getGeneratorOptions(), editorProject.getMenuTree(), true);
}

public void onCreateFontDialog(ActionEvent actionEvent) {
editorUI.showCreateFontUtility();
}

private record RecentlyUsedItem(String name, String path) {
public String toString() {
return name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.thecoderscorner.menu.editorui.controller;

import com.thecoderscorner.menu.editorui.generator.font.LoadedFont;
import com.thecoderscorner.menu.editorui.generator.font.UnicodeBlockMapping;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.stage.Stage;

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.thecoderscorner.menu.editorui.generator.font.AwtLoadedFont.NO_LOADED_FONT;

public class SelectUnicodeRangesController {
public TextField unicodeSearchField;
public ListView<UnicodeBlockWithSelection> unicodeRangeList;
public Button selectRangeButton;

private LoadedFont loadedFont = NO_LOADED_FONT;
private Optional<Set<UnicodeBlockMapping>> result = Optional.empty();

public void initialise(LoadedFont loadedFont, Set<UnicodeBlockMapping> currentEnabledMappings) {
var blocksForList = Arrays.stream(UnicodeBlockMapping.values())
.map(bm -> new UnicodeBlockWithSelection(bm, currentEnabledMappings.contains(bm)))
.toList();
unicodeRangeList.setItems(FXCollections.observableArrayList(blocksForList));

unicodeRangeList.setCellFactory(CheckBoxListCell.forListView(UnicodeBlockWithSelection::selectedProperty));
}

public Optional<Set<UnicodeBlockMapping>> getBlockMappings() {
return result;
}

public void onCancel(ActionEvent actionEvent) {
result = Optional.empty();
((Stage)unicodeSearchField.getScene().getWindow()).close();
}

public void onSelectRanges(ActionEvent actionEvent) {
result = Optional.of(unicodeRangeList.getItems().stream()
.filter(UnicodeBlockWithSelection::isSelected)
.map(UnicodeBlockWithSelection::getBlockMapping)
.collect(Collectors.toSet())
);
((Stage)unicodeSearchField.getScene().getWindow()).close();
}

public static class UnicodeBlockWithSelection {
private final UnicodeBlockMapping blockMapping;
private final BooleanProperty selected = new SimpleBooleanProperty();

public UnicodeBlockWithSelection(UnicodeBlockMapping blockMapping, boolean selected) {
this.blockMapping = blockMapping;
this.selected.set(selected);
}

public UnicodeBlockMapping getBlockMapping() {
return blockMapping;
}

public boolean isSelected() {
return selected.get();
}

public void setSelected(boolean sel) {
selected.set(sel);
}

public BooleanProperty selectedProperty() {
return selected;
}

@Override
public String toString() {
return blockMapping.toString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.thecoderscorner.menu.editorui.dialog;

import com.thecoderscorner.menu.editorui.controller.CreateFontUtilityController;
import com.thecoderscorner.menu.editorui.uimodel.CurrentProjectEditorUI;
import javafx.stage.Stage;

public class CreateFontUtilityDialog extends BaseDialogSupport<CreateFontUtilityController> {
private final String homeDirectory;
private CurrentProjectEditorUI editorUI;

public CreateFontUtilityDialog(Stage mainStage, CurrentProjectEditorUI editorUI, String homeDirectory) {
this.editorUI = editorUI;
this.homeDirectory = homeDirectory;
tryAndCreateDialog(mainStage, "/ui/createFontPanel.fxml", "Create Font Utility", true);
}

@Override
protected void initialiseController(CreateFontUtilityController controller) throws Exception {
controller.initialise(editorUI, homeDirectory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.thecoderscorner.menu.editorui.dialog;

import com.thecoderscorner.menu.domain.state.MenuTree;
import com.thecoderscorner.menu.editorui.controller.EditMenuInMenuItemController;
import com.thecoderscorner.menu.editorui.controller.SelectUnicodeRangesController;
import com.thecoderscorner.menu.editorui.generator.font.LoadedFont;
import com.thecoderscorner.menu.editorui.generator.font.UnicodeBlockMapping;
import com.thecoderscorner.menu.editorui.generator.parameters.MenuInMenuDefinition;
import javafx.stage.Stage;

import java.util.Optional;
import java.util.Set;

public class SelectUnicodeRangesDialog extends BaseDialogSupport<SelectUnicodeRangesController> {

private final LoadedFont loadedFont;
private Set<UnicodeBlockMapping> selectedRegions;

public SelectUnicodeRangesDialog(Stage stage, LoadedFont loadedFont, Set<UnicodeBlockMapping> selectedRegions) {
this.loadedFont = loadedFont;
this.selectedRegions = selectedRegions;
tryAndCreateDialog(stage, "/ui/selectUnicodeRanges.fxml", "Select Unicode Regions", true);
}

@Override
protected void initialiseController(SelectUnicodeRangesController controller) throws Exception {
controller.initialise(loadedFont, selectedRegions);
}

public Optional<Set<UnicodeBlockMapping>> getBlockMappings() {
return controller.getBlockMappings();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public String makeRtFunctionName(MenuItem item) {
return "fn" + makeNameToVar(item) + "RtCall";
}

public String makeNameFromVariable(String name) {
public static String makeNameFromVariable(String name) {
Collection<String> parts = Arrays.asList(name.split("[\\p{P}\\p{Z}\\t\\r\\n\\v\\f^]+"));
return parts.stream().map(StringHelper::capitaliseFirst).collect(Collectors.joining());
}
Expand Down
Loading

0 comments on commit f132374

Please sign in to comment.