Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font snippet #242

Merged
merged 3 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 201 additions & 6 deletions examples/css-snippets/dnd5e-compendium.css

Large diffs are not rendered by default.

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