Skip to content

Commit

Permalink
[tools] TTF/OTF support in asset sources and assets transformation an…
Browse files Browse the repository at this point in the history
…d hot reload improvements
  • Loading branch information
jeremyfa committed Dec 28, 2024
1 parent 2a3a0cd commit b86299b
Show file tree
Hide file tree
Showing 29 changed files with 616 additions and 155 deletions.
40 changes: 27 additions & 13 deletions plugins/clay/tools/src/backend/tools/ClayBackendTools.hx
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,35 @@ class ClayBackendTools implements tools.spec.BackendTools {

}

public function getDstAssetsPath(cwd:String, target:tools.BuildTarget, variant:String):String {

var outTargetPath = target.outPath('clay', cwd, context.debug, variant);

return switch (target.name) {
case 'mac':
Path.join([cwd, 'project', 'mac', context.project.app.name + '.app', 'Contents', 'Resources', 'assets']);
case 'ios':
Path.join([cwd, 'project', 'ios', 'project', 'assets', 'assets']);
case 'android':
Path.join([cwd, 'project', 'android', 'app', 'src', 'main', 'assets', 'assets']);
case 'windows' | 'linux' | 'web':
Path.join([cwd, 'project', target.name, 'assets']);
default:
Path.join([outTargetPath, 'assets']);
};

}

public function getTransformedAssetsPath(cwd:String, target:tools.BuildTarget, variant:String):String {

var outTargetPath = target.outPath('clay', cwd, context.debug, variant);
return Path.join([outTargetPath, 'transformedAssets']);

}

public function transformAssets(cwd:String, assets:Array<tools.Asset>, target:tools.BuildTarget, variant:String, listOnly:Bool, ?dstAssetsPath:String):Array<tools.Asset> {

var newAssets:Array<tools.Asset> = [];
var outTargetPath = target.outPath('clay', cwd, context.debug, variant);
var validDstPaths:Map<String,Bool> = new Map();
var assetsChanged = false;

Expand All @@ -217,18 +242,7 @@ class ClayBackendTools implements tools.spec.BackendTools {
var premultiplyAlpha = (target.name != 'web');

if (dstAssetsPath == null) {
switch (target.name) {
case 'mac':
dstAssetsPath = Path.join([cwd, 'project', 'mac', context.project.app.name + '.app', 'Contents', 'Resources', 'assets']);
case 'ios':
dstAssetsPath = Path.join([cwd, 'project', 'ios', 'project', 'assets', 'assets']);
case 'android':
dstAssetsPath = Path.join([cwd, 'project', 'android', 'app', 'src', 'main', 'assets', 'assets']);
case 'windows' | 'linux' | 'web':
dstAssetsPath = Path.join([cwd, 'project', target.name, 'assets']);
default:
dstAssetsPath = Path.join([outTargetPath, 'assets']);
}
dstAssetsPath = getDstAssetsPath(cwd, target, variant);
}

// Add/update missing assets
Expand Down
4 changes: 4 additions & 0 deletions plugins/font/ceramic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
plugin:
name: Font
tools: tools.FontPlugin

72 changes: 72 additions & 0 deletions plugins/font/tools/src/tools/FontPlugin.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package tools;

import haxe.io.Path;
import sys.FileSystem;
import sys.io.File;
import tools.Context;
import tools.Helpers.*;

using StringTools;

@:keep
class FontPlugin {

/// Tools

public function new() {}

public function init(context:Context):Void {

// Add tasks
context.addTask('font', new tools.tasks.font.Font());

// Add assets transformers
context.assetsTransformers.push({
transform: transformAssets
});

}

public function transformAssets(assets:Array<tools.Asset>, transformedAssetsPath:String, changedPaths:Array<String>):Array<tools.Asset> {

var result:Array<tools.Asset> = [];

for (asset in assets) {
final lowerCaseName = asset.name.toLowerCase();
if (lowerCaseName.endsWith('.ttf') || lowerCaseName.endsWith('.otf')) {
// Transform TTF/OTF to MSDF Bitmap Font

// Compute destination .fnt path
var baseName = asset.name.substring(0, asset.name.length - 4);
var fntName = baseName + '.fnt';
var pngName = baseName + '.png';
var dstFntPath = Path.join([transformedAssetsPath, fntName]);
var dstPngPath = Path.join([transformedAssetsPath, pngName]);

if (!Files.haveSameLastModified(asset.absolutePath, dstFntPath) || !Files.haveSameLastModified(asset.absolutePath, dstPngPath)) {
FontUtils.createBitmapFont({
fontPath: asset.absolutePath,
outputPath: transformedAssetsPath,
msdf: true,
quiet: true
});
Files.setToSameLastModified(asset.absolutePath, dstFntPath);
Files.setToSameLastModified(asset.absolutePath, dstPngPath);

changedPaths.push(dstFntPath);
changedPaths.push(dstPngPath);
}

result.push(new tools.Asset(fntName, transformedAssetsPath));
result.push(new tools.Asset(pngName, transformedAssetsPath));
}
else {
result.push(asset);
}
}

return result;

}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tools.tasks;
package tools;

import haxe.Json;
import haxe.io.Path;
Expand All @@ -8,61 +8,45 @@ import tools.Helpers.*;

using StringTools;

class Font extends Task {
class FontUtils {

public static function createBitmapFont(options:{
fontPath:String,
?outputPath:String,
?charset:String,
?quiet:Bool,
?charsetFile:String,
?msdf:Bool,
?msdfRange:Int,
?bold:Bool,
?italic:Bool,
?padding:Int,
?size:Float,
?offsetX:Float,
?offsetY:Float
}) {

var cwd = context.cwd;

var fontPath:String = options.fontPath;
var outputPath:String = options.outputPath;
var charset:String = options.charset;
var quiet:Bool = options.quiet ?? false;
var charsetFile:String = options.charsetFile;
var msdf:Bool = options.msdf ?? false;
var msdfRange:Int = options.msdfRange ?? 2;
var bold:Bool = options.bold ?? false;
var italic:Bool = options.italic ?? false;
var padding:Int = options.padding ?? 2;
var size:Float = options.size ?? 42;
var offsetX:Float = options.offsetX ?? 0;
var offsetY:Float = options.offsetY ?? 0;

/// Lifecycle

override public function help(cwd:String):Array<Array<String>> {

return [
['--font <path to font>', 'The ttf/otf font file to convert'],
['--out <output directory>', 'The output directory'],
['--msdf', 'If used, export with multichannel distance field'],
['--msdf-range', 'Sets the distance field range in pixels (default: 2)'],
['--size <font size>', 'The font size to export (default: 42)'],
['--factor <factor>', 'A precision factor (advanced usage, default: 4)'],
['--charset', 'Characters to use as charset'],
['--charset-file', 'A text file containing characters to use as charset'],
['--offset-x', 'Move every character by this X offset'],
['--offset-y', 'Move every character by this Y offset']
];

}

override public function info(cwd:String):String {

return "Utility to convert ttf/otf font to bitmap font compatible with ceramic";

}

override function run(cwd:String, args:Array<String>):Void {

var fontPath = extractArgValue(args, 'font');
var outputPath = extractArgValue(args, 'out');
var charset = extractArgValue(args, 'charset');
var charsetFile = extractArgValue(args, 'charset-file');
var msdf = extractArgFlag(args, 'msdf');
var msdfRange = extractArgValue(args, 'msdf-range') != null ? Math.round(Std.parseFloat(extractArgValue(args, 'msdf-range'))) : 2;
var bold = extractArgFlag(args, 'bold');
var italic = extractArgFlag(args, 'italic');
var outputInTmp = extractArgFlag(args, 'output-in-tmp');
var padding:Int = extractArgValue(args, 'padding') != null ? Math.round(Std.parseFloat(extractArgValue(args, 'padding'))) : 2;
var size:Float = extractArgValue(args, 'size') != null ? Std.parseFloat(extractArgValue(args, 'size')) : 42;
var offsetX:Float = extractArgValue(args, 'offset-x') != null ? Std.parseFloat(extractArgValue(args, 'offset-x')) : 0;
var offsetY:Float = extractArgValue(args, 'offset-y') != null ? Std.parseFloat(extractArgValue(args, 'offset-y')) : 0;

if (fontPath == null) {
fail('--font argument is required');
}
if (!Path.isAbsolute(fontPath))
fontPath = Path.normalize(Path.join([cwd, fontPath]));

if (outputPath == null) {
if (outputInTmp) {
outputPath = TempDirectory.tempDir('font') ?? Path.join([cwd, '.tmp']);
}
else {
outputPath = cwd;
}
}
if (!Path.isAbsolute(outputPath))
outputPath = Path.normalize(Path.join([cwd, outputPath]));

if (charset == null) {
if (charsetFile != null) {
Expand All @@ -77,12 +61,6 @@ class Font extends Task {
}
}

if (!Path.isAbsolute(fontPath))
fontPath = Path.normalize(Path.join([cwd, fontPath]));

if (!Path.isAbsolute(outputPath))
outputPath = Path.normalize(Path.join([cwd, outputPath]));

var tmpDir = TempDirectory.tempDir('font') ?? Path.join([cwd, '.tmp']);
if (FileSystem.exists(tmpDir)) {
Files.deleteRecursive(tmpDir);
Expand Down Expand Up @@ -119,7 +97,7 @@ class Font extends Task {
var tmpImagePath = Path.join([tmpDir, '$rawName.png']);
var tmpJsonPath = Path.join([tmpDir, '$rawName.json']);

command(msdfAtlasGen, [
final result = command(msdfAtlasGen, [
'-charset', tmpCharsetPath,
'-font', tmpFontPath,
'-type', msdf ? 'msdf' : 'softmask',
Expand All @@ -131,7 +109,7 @@ class Font extends Task {
].concat(msdf ? [
'-pxrange', '$msdfRange',
] : []), {
mute: outputInTmp
mute: quiet
});

// Make the image transparent, if not using msdf
Expand Down Expand Up @@ -254,11 +232,9 @@ class Font extends Task {
// Remove temporary files
Files.deleteRecursive(tmpDir);

// Print output path, if using "output in tmp"
if (outputInTmp) {
print(outputPath);
}
// Return `true` if the command didn't fail
return result.status == 0;

}

}
}
80 changes: 80 additions & 0 deletions plugins/font/tools/src/tools/tasks/font/Font.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package tools.tasks.font;

import haxe.Json;
import haxe.io.Path;
import sys.FileSystem;
import sys.io.File;
import tools.Helpers.*;

using StringTools;

class Font extends Task {

/// Lifecycle

override public function help(cwd:String):Array<Array<String>> {

return [
['--font <path to font>', 'The ttf/otf font file to convert'],
['--out <output directory>', 'The output directory'],
['--msdf', 'If used, export with multichannel distance field'],
['--msdf-range', 'Sets the distance field range in pixels (default: 2)'],
['--size <font size>', 'The font size to export (default: 42)'],
['--factor <factor>', 'A precision factor (advanced usage, default: 4)'],
['--charset', 'Characters to use as charset'],
['--charset-file', 'A text file containing characters to use as charset'],
['--offset-x', 'Move every character by this X offset'],
['--offset-y', 'Move every character by this Y offset']
];

}

override public function info(cwd:String):String {

return "Utility to convert ttf/otf font to bitmap font compatible with ceramic";

}

override function run(cwd:String, args:Array<String>):Void {

var fontPath = extractArgValue(args, 'font');
var outputPath = extractArgValue(args, 'out');
var charset = extractArgValue(args, 'charset');
var quiet = extractArgFlag(args, 'quiet');
var charsetFile = extractArgValue(args, 'charset-file');
var msdf = extractArgFlag(args, 'msdf');
var msdfRange = extractArgValue(args, 'msdf-range') != null ? Math.round(Std.parseFloat(extractArgValue(args, 'msdf-range'))) : 2;
var bold = extractArgFlag(args, 'bold');
var italic = extractArgFlag(args, 'italic');
var padding:Int = extractArgValue(args, 'padding') != null ? Math.round(Std.parseFloat(extractArgValue(args, 'padding'))) : 2;
var size:Float = extractArgValue(args, 'size') != null ? Std.parseFloat(extractArgValue(args, 'size')) : 42;
var offsetX:Float = extractArgValue(args, 'offset-x') != null ? Std.parseFloat(extractArgValue(args, 'offset-x')) : 0;
var offsetY:Float = extractArgValue(args, 'offset-y') != null ? Std.parseFloat(extractArgValue(args, 'offset-y')) : 0;

if (fontPath == null) {
fail('--font argument is required');
}

if (outputPath == null) {
outputPath = cwd;
}

FontUtils.createBitmapFont({
fontPath: fontPath,
outputPath: outputPath,
charset: charset,
quiet: quiet,
charsetFile: charsetFile,
msdf: msdf,
msdfRange: msdfRange,
bold: bold,
italic: italic,
padding: padding,
size: size,
offsetX: offsetX,
offsetY: offsetY
});

}

}
16 changes: 15 additions & 1 deletion plugins/headless/tools/src/backend/tools/HeadlessBackendTools.hx
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,27 @@ class HeadlessBackendTools implements tools.spec.BackendTools {

}

public function getDstAssetsPath(cwd:String, target:tools.BuildTarget, variant:String):String {

var hxmlProjectPath = target.outPath('headless', cwd, context.debug, variant);
return Path.join([hxmlProjectPath, 'assets']);

}

public function getTransformedAssetsPath(cwd:String, target:tools.BuildTarget, variant:String):String {

var hxmlProjectPath = target.outPath('headless', cwd, context.debug, variant);
return Path.join([hxmlProjectPath, 'transformedAssets']);

}

public function transformAssets(cwd:String, assets:Array<tools.Asset>, target:tools.BuildTarget, variant:String, listOnly:Bool, ?dstAssetsPath:String):Array<tools.Asset> {

var newAssets:Array<tools.Asset> = [];
var hxmlProjectPath = target.outPath('headless', cwd, context.debug, variant);
var validDstPaths:Map<String,Bool> = new Map();
if (dstAssetsPath == null) {
dstAssetsPath = Path.join([hxmlProjectPath, 'assets']);
dstAssetsPath = getDstAssetsPath(cwd, target, variant);
}

var assetsPrefix = '';
Expand Down
Loading

0 comments on commit b86299b

Please sign in to comment.