Skip to content

Commit

Permalink
✨ Generate CSS snippet for referenced fonts
Browse files Browse the repository at this point in the history
  • Loading branch information
ebullient committed Oct 17, 2023
1 parent 71ddc24 commit 36afd0e
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 12 deletions.
53 changes: 53 additions & 0 deletions src/main/java/dev/ebullient/convert/io/FontRef.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.ebullient.convert.io;

public class FontRef {
/** Font family */
public final String fontFamily;
/** Path to font source (unresolved local or remote) */
public final String sourcePath;

boolean hasTextReference = false;

private FontRef(String fontFamily, String sourcePath) {
this.fontFamily = fontFamily;
this.sourcePath = sourcePath;
}

public void addTextReference() {
hasTextReference = true;
}

public boolean hasTextReference() {
return hasTextReference;
}

@Override
public String toString() {
return "FontRef [fontFamily=" + fontFamily + ", sourcePath=" + sourcePath + "]";
}

public static String fontFamily(String fontPath) {
fontPath = fontPath.trim();
int pos1 = fontPath.lastIndexOf('/');
int pos2 = fontPath.lastIndexOf('.');
if (pos1 > 0 && pos2 > 0) {
fontPath = fontPath.substring(pos1 + 1, pos2);
} else if (pos1 > 0) {
fontPath = fontPath.substring(pos1 + 1);
} else if (pos2 > 0) {
fontPath = fontPath.substring(0, pos2);
}
return fontPath;
}

public static FontRef of(String fontString) {
return of(fontFamily(fontString), fontString);
}

public static FontRef of(String fontFamily, String fontString) {
if (fontString == null || fontString.isEmpty()) {
return null;
}
return new FontRef(fontFamily, fontString);
}
}
21 changes: 21 additions & 0 deletions src/main/java/dev/ebullient/convert/io/Templates.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package dev.ebullient.convert.io;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Collection;

import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -103,4 +105,23 @@ public String renderIndex(String name, Collection<FileMap> resources) {
return "%% ERROR: " + message + " %%";
}
}

public String renderCss(FontRef fontRef, InputStream data) throws IOException {
Template tpl = customTemplateOrDefault("css-font.txt");
try {
String encoded = Base64.getEncoder().encodeToString(data.readAllBytes());
int extpos = fontRef.sourcePath.lastIndexOf(".");
String type = fontRef.sourcePath.substring(extpos + 1);
return tpl
.data("fontFamily", fontRef.fontFamily)
.data("type", type)
.data("encoded", encoded)
.render();
} catch (TemplateException tex) {
Throwable cause = tex.getCause();
String message = cause != null ? cause.toString() : tex.toString();
tui.error(tex, message);
return "%% ERROR: " + message + " %%";
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/dev/ebullient/convert/io/Tui.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package dev.ebullient.convert.io;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -55,6 +57,12 @@

@ApplicationScoped
public class Tui {
static Tui instance;

public static Tui instance() {
return instance;
}

public final static TypeReference<List<String>> LIST_STRING = new TypeReference<>() {
};
public final static TypeReference<List<Integer>> LIST_INT = new TypeReference<>() {
Expand Down Expand Up @@ -176,6 +184,8 @@ public Tui() {
this.err = new PrintWriter(System.err);
this.debug = false;
this.verbose = true;

Tui.instance = this;
}

public void init(CommandSpec spec, boolean debug, boolean verbose) {
Expand Down Expand Up @@ -304,6 +314,33 @@ public Optional<Path> resolvePath(Path path) {
.findFirst();
}

public void copyFonts(Collection<FontRef> fonts, Map<String, String> fallbackPaths) {
for (FontRef fontRef : fonts) {
Path targetPath = output.resolve(Path.of("css-snippets", slugify(fontRef.fontFamily) + ".css"));
targetPath.getParent().toFile().mkdirs();

printlnf("⏱️ Generating CSS snippet for %s", fontRef.sourcePath);
if (fontRef.sourcePath.startsWith("http")) {
try (InputStream is = URI.create(fontRef.sourcePath.replace(" ", "%20")).toURL().openStream()) {
Files.writeString(targetPath, templates.renderCss(fontRef, is));
} catch (IOException e) {
errorf(e, "Unable to copy font from %s to %s", fontRef.sourcePath, targetPath);
}
} else {
Optional<Path> resolvedSource = resolvePath(Path.of(fontRef.sourcePath));
if (resolvedSource.isEmpty()) {
errorf("Unable to find font '%s'", fontRef.sourcePath);
continue;
}
try (BufferedInputStream is = new BufferedInputStream(Files.newInputStream(resolvedSource.get()))) {
Files.writeString(targetPath, templates.renderCss(fontRef, is));
} catch (IOException e) {
errorf(e, "Unable to copy font from %s to %s", fontRef.sourcePath, targetPath);
}
}
}
}

public void copyImages(Collection<ImageRef> images, Map<String, String> fallbackPaths) {
for (ImageRef image : images) {
Path targetPath = output.resolve(image.targetFilePath());
Expand Down
32 changes: 23 additions & 9 deletions src/main/java/dev/ebullient/convert/tools/JsonNodeReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ default boolean existsIn(JsonNode source) {
return source.has(this.nodeName());
}

default boolean isArrayIn(JsonNode source) {
if (source == null || !source.has(this.nodeName())) {
return false;
}
return source.get(this.nodeName()).isArray();
}

default boolean isObjectIn(JsonNode source) {
if (source == null || !source.has(this.nodeName())) {
return false;
}
return source.get(this.nodeName()).isObject();
}

default <T> T fieldFromTo(JsonNode source, Class<T> classTarget, Tui tui) {
return tui.readJsonValue(source.get(this.nodeName()), classTarget);
}
Expand Down Expand Up @@ -268,24 +282,24 @@ default boolean valueEquals(JsonNode previous, JsonNode next) {
}

default ArrayNode withArrayFrom(JsonNode source) {
if (source == null || !source.has(this.nodeName())) {
return Tui.MAPPER.createArrayNode();
if (isArrayIn(source)) {
return source.withArray(this.nodeName());
}
return source.withArray(this.nodeName());
return Tui.MAPPER.createArrayNode();
}

default Iterable<JsonNode> iterateArrayFrom(JsonNode source) {
if (source == null || !source.has(this.nodeName())) {
return List.of();
if (isArrayIn(source)) {
return () -> source.withArray(this.nodeName()).elements();
}
return () -> source.withArray(this.nodeName()).elements();
return List.of();
}

default Iterable<Entry<String, JsonNode>> iterateFieldsFrom(JsonNode source) {
if (source == null) {
return List.of();
if (isObjectIn(source)) {
return () -> source.get(this.nodeName()).fields();
}
return source::fields;
return List.of();
}

/** Destructive! */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public interface JsonTextReplacement extends JsonTextConverter<Tools5eIndexType>
Pattern dicePattern = Pattern.compile("\\{@(dice|damage) ([^{}]+)}");

Pattern chancePattern = Pattern.compile("\\{@chance ([^}]+)}");
Pattern fontPattern = Pattern.compile("\\{@font ([^}]+)}");
Pattern homebrewPattern = Pattern.compile("\\{@homebrew ([^}]+)}");
Pattern quickRefPattern = Pattern.compile("\\{@quickref ([^}]+)}");
Pattern notePattern = Pattern.compile("\\{@note (\\*|Note:)?\\s?([^}]+)}");
Expand Down Expand Up @@ -186,6 +187,17 @@ default String _replaceTokenText(String input, boolean nested) {
result = linkifyPattern.matcher(result)
.replaceAll(this::linkify);

result = fontPattern.matcher(result)
.replaceAll((match) -> {
String[] parts = match.group(1).split("\\|");
String fontFamily = Tools5eSources.getFontReference(parts[1]);
if (fontFamily != null) {
return String.format("<span style=\"font-family: %s\">%s</span>",
fontFamily, parts[0]);
}
return parts[0];
});

try {
result = result
.replace("{@hitYourSpellAttack}", "the summoner's spell attack modifier")
Expand Down Expand Up @@ -216,8 +228,8 @@ default String _replaceTokenText(String input, boolean nested) {
.replaceAll("\\{@cult ([^|}]+)}", "$1")
.replaceAll("\\{@language ([^|}]+)\\|?[^}]*}", "$1")
.replaceAll("\\{@book ([^}|]+)\\|?[^}]*}", "\"$1\"")
.replaceAll("\\{@hit ([+-][^}<]+)}", "$1")
.replaceAll("\\{@hit ([^}<]+)}", "+$1")
.replaceAll("\\{@(hit|h) ([+-][^}<]+)}", "$2")
.replaceAll("\\{@(hit|h) ([^}<]+)}", "+$2")
.replaceAll("\\{@h}", "*Hit:* ")
.replaceAll("\\{@m}", "*Miss:* ")
.replaceAll("\\{@atk a}", "*Area Attack:*")
Expand All @@ -235,6 +247,7 @@ default String _replaceTokenText(String input, boolean nested) {
.replaceAll("\\{@atk ms}", "*Melee Spell Attack:*")
.replaceAll("\\{@atk rs}", "*Ranged Spell Attack:*")
.replaceAll("\\{@atk ms,rs}", "*Melee or Ranged Spell Attack:*")
.replaceAll("\\{@spell\\s*}", "") // error in homebrew
.replaceAll("\\{@color ([^|}]+)\\|?[^}]*}", "$1")
.replaceAll("\\{@style ([^|}]+)\\|?[^}]*}", "$1")
.replaceAll("\\{@b ([^}]+?)}", "**$1**")
Expand Down Expand Up @@ -269,6 +282,7 @@ default String _replaceTokenText(String input, boolean nested) {
String[] parts = match.group(1).split("\\|");
if (parts[0].contains("<sup>")) {
// This already assumes what the footnote name will be
// TODO: Note content is lost on this path at the moment
return String.format("%s", parts[0]);
}
if (parts.length > 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ private void indexTypes(String filename, JsonNode node) {
Tools5eIndexType.spellFluff.withArrayFrom(node, this::addToIndex);
Tools5eIndexType.vehicleFluff.withArrayFrom(node, this::addToIndex);

Tools5eIndexType.language.withArrayFrom(node, this::addToIndex);

Tools5eIndexType.itemEntry.withArrayFrom(node, this::addToIndex);
Tools5eIndexType.itemTypeAdditionalEntries.withArrayFrom(node, this::addToIndex);
Tools5eIndexType.magicvariant.withArrayFrom(node, this::addToIndex);
Expand Down Expand Up @@ -246,6 +248,7 @@ private boolean addHomebrewSourcesIfPresent(String filename, JsonNode node) {
metaTypes.setSkillType(skillName, skill);
}
}
Tools5eSources.addFonts(SourceField._meta.getFrom(node), HomebrewFields.fonts);
return true;
}

Expand Down Expand Up @@ -307,7 +310,9 @@ void addToIndex(Tools5eIndexType type, JsonNode node) {
SourceAndPage sp = new SourceAndPage(node);
tableIndex.computeIfAbsent(sp, k -> new ArrayList<>()).add(node);
}

if (type == Tools5eIndexType.language && HomebrewFields.fonts.existsIn(node)) {
Tools5eSources.addFonts(node, HomebrewFields.fonts);
}
if (node.has("srd")) {
srdKeys.add(key);
}
Expand Down Expand Up @@ -1172,6 +1177,7 @@ public void setItemProperty(String key, CustomItemProperty value) {

enum HomebrewFields implements JsonNodeReader {
abbreviation,
fonts,
full,
json,
optionalFeatureTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public enum Tools5eIndexType implements IndexType, JsonNodeReader {
itemType,
itemTypeAdditionalEntries,
itemProperty,
language,
legendaryGroup,
magicvariant,
monster,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public Tools5eMarkdownConverter writeNotesAndTables() {

public Tools5eMarkdownConverter writeImages() {
index.tui().copyImages(Tools5eSources.getImages(), fallbackPaths);
index.tui().copyFonts(Tools5eSources.getFonts(), fallbackPaths);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;

import dev.ebullient.convert.config.TtrpgConfig;
import dev.ebullient.convert.io.FontRef;
import dev.ebullient.convert.io.Tui;
import dev.ebullient.convert.qute.ImageRef;
import dev.ebullient.convert.qute.QuteBase;
import dev.ebullient.convert.tools.CompendiumSources;
import dev.ebullient.convert.tools.IndexType;
import dev.ebullient.convert.tools.JsonNodeReader;
import dev.ebullient.convert.tools.JsonTextConverter.SourceField;
import dev.ebullient.convert.tools.ToolsIndex.TtrpgValue;
import dev.ebullient.convert.tools.dnd5e.JsonSource.JsonMediaHref;
Expand All @@ -27,6 +31,7 @@ public class Tools5eSources extends CompendiumSources {

private static final Map<String, Tools5eSources> keyToSources = new HashMap<>();
private static final Map<Path, ImageRef> imageSourceToRef = new HashMap<>();
private static final Map<String, FontRef> fontSourceToRef = new HashMap<>();
private static final Map<String, List<QuteBase>> keyToInlineNotes = new HashMap<>();

public static Tools5eSources findSources(String key) {
Expand Down Expand Up @@ -86,6 +91,51 @@ public void addInlineNote(QuteBase note) {
keyToInlineNotes.computeIfAbsent(this.key, k -> new ArrayList<>()).add(note);
}

public static Collection<FontRef> getFonts() {
return fontSourceToRef.values().stream()
.filter(FontRef::hasTextReference)
.toList();
}

public static void addFonts(JsonNode source, JsonNodeReader field) {
if (field.isArrayIn(source)) {
for (JsonNode font : field.iterateArrayFrom(source)) {
addFont(font.asText());
}
} else if (field.isObjectIn(source)) {
for (Entry<String, JsonNode> font : field.iterateFieldsFrom(source)) {
addFont(font.getKey(), font.getValue().asText());
}
}
}

static void addFont(String fontFamily, String fontString) {
FontRef ref = FontRef.of(fontFamily, fontString);
if (ref == null) {
Tui.instance().warnf("Font '%s' is invalid, empty, or not found", fontString);
} else {
FontRef previous = fontSourceToRef.putIfAbsent(fontFamily, ref);
if (previous != null) {
Tui.instance().warnf("Font '%s' is already defined as '%s'", fontString, previous);
}
}
}

static void addFont(String fontString) {
String fontFamily = FontRef.fontFamily(fontString);
addFont(fontFamily, fontString);
}

public static String getFontReference(String fontString) {
String fontFamily = FontRef.fontFamily(fontString);
FontRef ref = fontSourceToRef.get(fontFamily);
if (ref == null) {
return null;
}
ref.addTextReference();
return fontFamily;
}

final boolean srd;
final boolean basicRules;
final Tools5eIndexType type;
Expand Down
Loading

0 comments on commit 36afd0e

Please sign in to comment.