From 99195a570dc8700712911280d4bff36490c0b5ac Mon Sep 17 00:00:00 2001 From: ArthurHeitmann <37270165+ArthurHeitmann@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:35:57 +0200 Subject: [PATCH] Add new chars to FTB --- assets/FontAtlasGenerator | 2 +- lib/fileTypeUtils/ftb/ftbIO.dart | 14 +- lib/fileTypeUtils/textures/ddsConverter.dart | 2 + lib/fileTypeUtils/ttf/ttf.dart | 53 +++++ .../openFiles/types/FtbFileData.dart | 213 +++++++++++++----- .../openFiles/types/McdFileData.dart | 3 +- .../filesView/types/fonts/FontsManager.dart | 38 ++-- .../types/fonts/fontOverridesApply.dart | 31 ++- .../filesView/types/fonts/ftbEditor.dart | 54 +++-- 9 files changed, 316 insertions(+), 94 deletions(-) diff --git a/assets/FontAtlasGenerator b/assets/FontAtlasGenerator index 0cb0b21a..b9564309 160000 --- a/assets/FontAtlasGenerator +++ b/assets/FontAtlasGenerator @@ -1 +1 @@ -Subproject commit 0cb0b21af4ecc396988fc38ceed108065ad90942 +Subproject commit b956430964a49b405bb1986a5a90159f7984e86e diff --git a/lib/fileTypeUtils/ftb/ftbIO.dart b/lib/fileTypeUtils/ftb/ftbIO.dart index 733fe437..92b1114f 100644 --- a/lib/fileTypeUtils/ftb/ftbIO.dart +++ b/lib/fileTypeUtils/ftb/ftbIO.dart @@ -1,7 +1,9 @@ import '../utils/ByteDataWrapper.dart'; class FtbFileHeader { - List magic; + List start; + int globalKerning; + int null0; int texturesCount; int unknown; int charsCount; @@ -9,10 +11,12 @@ class FtbFileHeader { int charsOffset; int charsOffset2; - FtbFileHeader(this.magic, this.texturesCount, this.unknown, this.charsCount, this.texturesOffset, this.charsOffset, this.charsOffset2); + FtbFileHeader(this.start, this.globalKerning, this.null0, this.texturesCount, this.unknown, this.charsCount, this.texturesOffset, this.charsOffset, this.charsOffset2); FtbFileHeader.read(ByteDataWrapper bytes) : - magic = bytes.readUint8List(118), + start = bytes.readUint8List(114), + globalKerning = bytes.readInt16(), + null0 = bytes.readUint16(), texturesCount = bytes.readUint16(), unknown = bytes.readUint16(), charsCount = bytes.readUint16(), @@ -21,8 +25,10 @@ class FtbFileHeader { charsOffset2 = bytes.readUint32(); void write(ByteDataWrapper bytes) { - for (var b in magic) + for (var b in start) bytes.writeUint8(b); + bytes.writeInt16(globalKerning); + bytes.writeUint16(null0); bytes.writeUint16(texturesCount); bytes.writeUint16(unknown); bytes.writeUint16(charsCount); diff --git a/lib/fileTypeUtils/textures/ddsConverter.dart b/lib/fileTypeUtils/textures/ddsConverter.dart index 9d3f3bef..dc1ee101 100644 --- a/lib/fileTypeUtils/textures/ddsConverter.dart +++ b/lib/fileTypeUtils/textures/ddsConverter.dart @@ -12,6 +12,8 @@ Future pngToDds(String ddsPath, String pngPath) async { [pngPath, "-define", "dds:mipmaps=0", ddsPath] ); if (result.exitCode != 0) { + messageLog.add("stdout: ${result.stdout}"); + messageLog.add("stderr: ${result.stderr}"); messageLog.add("Failed to convert texture to DDS: ${result.stderr}"); throw Exception("Failed to convert texture to DDS: ${result.stderr}"); } diff --git a/lib/fileTypeUtils/ttf/ttf.dart b/lib/fileTypeUtils/ttf/ttf.dart index 8957d74e..fbae378a 100644 --- a/lib/fileTypeUtils/ttf/ttf.dart +++ b/lib/fileTypeUtils/ttf/ttf.dart @@ -123,6 +123,13 @@ class Cmap { numTables = bytes.readUint16(); encodingRecords = List.generate(numTables, (i) => EncodingRecord.read(bytes, cmapOffset)); } + + Iterable allChars() { + return encodingRecords + .map((r) => r.allChars()) + .expand((c) => c) + .toSet(); + } } class EncodingRecord { @@ -140,6 +147,10 @@ class EncodingRecord { format = CmapFormat.read(bytes); bytes.position = previous; } + + Iterable allChars() { + return format?.allChars() ?? []; + } } abstract class CmapFormat { late final int format; @@ -163,6 +174,8 @@ abstract class CmapFormat { } int? getGlyphIndex(String char); + + Iterable allChars(); } class CmapFormat0 implements CmapFormat { late final int format; @@ -186,6 +199,15 @@ class CmapFormat0 implements CmapFormat { return null; return glyphIndexArray[code]; } + + @override + Iterable allChars() { + return glyphIndexArray + .asMap() + .entries + .where((e) => e.value != 0) + .map((e) => String.fromCharCode(e.key)); + } } class CmapFormat4 implements CmapFormat { late final int format; @@ -245,6 +267,11 @@ class CmapFormat4 implements CmapFormat { int code = char.codeUnitAt(0); return glyphToIndexMap[code]; } + + @override + Iterable allChars() { + return glyphToIndexMap.keys.map((c) => String.fromCharCode(c)); + } } class CmapFormat6 implements CmapFormat { late final int format; @@ -272,6 +299,15 @@ class CmapFormat6 implements CmapFormat { return null; return glyphIndexArray[code - firstCode]; } + + @override + Iterable allChars() { + return glyphIndexArray + .asMap() + .entries + .where((e) => e.value != 0) + .map((e) => String.fromCharCode(e.key + firstCode)); + } } class CmapFormat1213 implements CmapFormat { late final int format; @@ -301,6 +337,14 @@ class CmapFormat1213 implements CmapFormat { else return group.id; } + + @override + Iterable allChars() { + return groupRecords + .map((group) => Iterable.generate(group.endCharCode - group.startCharCode + 1, (i) => group.startCharCode + i)) + .expand((c) => c) + .map((c) => String.fromCharCode(c)); + } } class GroupRecord extends Range { late final int id; @@ -325,6 +369,11 @@ class CmapFormatUnknown implements CmapFormat { int? getGlyphIndex(String char) { return null; } + + @override + Iterable allChars() { + return []; + } } @@ -1025,4 +1074,8 @@ class TtfFile { double getKerningScaled(String left, String right, int fontSize) { return getKerning(left, right) * fontSize / head.unitsPerEm; } + + Iterable allChars() { + return cmap.allChars(); + } } diff --git a/lib/stateManagement/openFiles/types/FtbFileData.dart b/lib/stateManagement/openFiles/types/FtbFileData.dart index 7280e72a..da8618b4 100644 --- a/lib/stateManagement/openFiles/types/FtbFileData.dart +++ b/lib/stateManagement/openFiles/types/FtbFileData.dart @@ -11,11 +11,15 @@ import '../../../fileTypeUtils/ftb/ftbIO.dart'; import '../../../fileTypeUtils/textures/fontAtlasGenerator.dart'; import '../../../fileTypeUtils/textures/fontAtlasGeneratorTypes.dart'; import '../../../fileTypeUtils/textures/ddsConverter.dart'; +import '../../../fileTypeUtils/ttf/ttf.dart'; +import '../../../fileTypeUtils/utils/ByteDataWrapper.dart'; import '../../../fileTypeUtils/wta/wtaReader.dart'; import '../../../utils/assetDirFinder.dart'; import '../../../utils/utils.dart'; import '../../../widgets/filesView/FileType.dart'; +import '../../Property.dart'; import '../../changesExporter.dart'; +import '../../events/statusInfo.dart'; import '../../undoable.dart'; import '../openFileTypes.dart'; import 'McdFileData.dart'; @@ -44,6 +48,36 @@ class FtbFileData extends OpenFileData { var datDir = dirname(path); changedDatFiles.add(datDir); await super.save(); + await processChangedFiles(); + } + + Future addCharsFromFront(String fontPath) async { + Iterable fontChars; + try { + var bytes = await ByteDataWrapper.fromFile(fontPath); + var ttf = TtfFile.read(bytes); + fontChars = ttf.allChars(); + } catch (e) { + showToast("Failed to read font file"); + rethrow; + } + ftbData!.pendingNewChars.clear(); + for (var char in fontChars) { + if (ftbData!.chars.any((c) => c.char == char)) + continue; + var code = char.codeUnitAt(0); + if (code < 0x20 || code >= 0x7F && code < 0xA0 || code >= 0x7FFF) + continue; + ftbData!.pendingNewChars.add(FtbPendingChar(char, fontPath)); + } + var newChars = ftbData!.pendingNewChars.map((c) => c.char).toList(); + if (newChars.isEmpty) { + showToast("No new chars to add"); + return; + } + await save(); + showToast("Added ${newChars.length} new chars"); + messageLog.add("New chars: ${newChars.join(", ")}"); } @override @@ -83,19 +117,52 @@ class FtbTexture { FtbTexture(this.width, this.height); } -class FtbChar { - String char; +abstract class FtbCharBase { + final String char; + + FtbCharBase(this.char); + + CliImgOperation getImgOperation(int i, bool hasOverride, CliImgOperationDrawFromTexture? Function() getFallback); +} +class FtbChar extends FtbCharBase { int texId; int width; int height; int x; int y; - FtbChar(this.char, this.texId, this.width, this.height, this.x, this.y); + FtbChar(super.char, this.texId, this.width, this.height, this.x, this.y); + + @override + CliImgOperation getImgOperation(int i, bool hasOverride, CliImgOperationDrawFromTexture? Function() getFallback) { + if (!hasOverride) + return getFallback()!; + return CliImgOperationDrawFromFont( + i, char, 0, getFallback(), + ); + } +} + +class FtbPendingChar extends FtbCharBase { + final String fontPath; + + FtbPendingChar(super.char, this.fontPath); + + @override + CliImgOperation getImgOperation(int i, bool hasOverride, CliImgOperationDrawFromTexture? Function() getFallback) { + return CliImgOperationDrawFromFont( + i, char, 1, null, + ); + } + + FtbChar toDefaultChar() { + return FtbChar(char, 0, 0, 0, 0, 0); + } } class FtbData extends ChangeNotifier { final List _magic; + final NumberProp kerning; List textures; List chars; String path; @@ -103,9 +170,11 @@ class FtbData extends ChangeNotifier { String wtpPath; WtaFile wtaFile; int fontId; + List pendingNewChars = []; - FtbData(this._magic, this.textures, this.chars, this.path, this.wtaPath, this.wtpPath, this.wtaFile) - : fontId = int.parse(basenameWithoutExtension(path).substring(5)); + FtbData(this._magic, int kerning, this.textures, this.chars, this.path, this.wtaPath, this.wtpPath, this.wtaFile) : + kerning = NumberProp(kerning, true, fileId: null), + fontId = int.parse(basenameWithoutExtension(path).substring(5)); static Future fromFtbFile(String path) async{ var name = basenameWithoutExtension(path); @@ -125,7 +194,8 @@ class FtbData extends ChangeNotifier { var ftbFile = await FtbFile.fromFile(path); var ftbData = FtbData( - ftbFile.header.magic, + ftbFile.header.start, + ftbFile.header.globalKerning, ftbFile.textures.map((e) => FtbTexture(e.width, e.height)).toList(), ftbFile.chars.map((e) => FtbChar(e.char, e.texId, e.width, e.height, e.u, e.v)).toList(), path, @@ -146,50 +216,50 @@ class FtbData extends ChangeNotifier { return ftbData; } - Future, int>> generateTextureBatch(int i, List chars, CliFontOptions font, String ddsPath) async { + Future, int>> generateTextureBatch(int batchI, List chars, CliFontOptions? fontOverride, CliFontOptions? newFont, List textures, String ddsPath) async { List imgOperations = []; - var fallbackSymbols = McdData.availableFonts[fontId]?.supportedSymbols; + var usedCharsMap = { + for (var c in chars) + if (c is FtbChar) + c.char: c + }; + var hasOverride = fontOverride != null; for (int i = 0; i < chars.length; i++) { var char = chars[i]; - CliImgOperationDrawFromTexture? fallback; - var fontSymbol = fallbackSymbols?[char.char.codeUnitAt(0)]; - if (fontSymbol != null) { - fallback = CliImgOperationDrawFromTexture( - i, 0, - fontSymbol.getX(), fontSymbol.getY(), - fontSymbol.getWidth(), fontSymbol.getHeight(), - 1.0, - ); - } - imgOperations.add(CliImgOperationDrawFromFont( - i, char.char, fontId, fallback, - )); + imgOperations.add(char.getImgOperation(i, hasOverride, () { + var currentChar = usedCharsMap[char.char]; + if (currentChar != null) + return CliImgOperationDrawFromTexture( + i, currentChar.texId, + currentChar.x, currentChar.y, + currentChar.width, currentChar.height, + 1.0, + ); + return null; + })); } - var fallbackTexPath = McdData.availableFonts[fontId]!.atlasTexturePath; var texPngPath = "${ddsPath.substring(0, ddsPath.length - 4)}.png"; + var fonts = { + if (fontOverride != null) 0: fontOverride, + if (newFont != null) 1: newFont, + }; var cliArgs = FontAtlasGenCliOptions( - texPngPath, [fallbackTexPath], + texPngPath, textures, McdData.fontAtlasLetterSpacing.value.toInt(), 2048, - { fontId: font }, imgOperations + fonts, + imgOperations, ); var atlasInfo = await runFontAtlasGenerator(cliArgs); - // convert png to dds (.wtp) - var result = await Process.run( - magickBinPath!, - [texPngPath, "-define", "dds:mipmaps=0", ddsPath], - ); - if (result.exitCode != 0) { - showToast("ImageMagick failed"); - print(result.stdout); - print(result.stderr); - throw Exception("ImageMagick failed"); - } + await pngToDds(ddsPath, texPngPath); - print("Generated font atlas $i with ${atlasInfo.symbols.length} symbols"); + messageLog.add("Generated font atlas $batchI with ${atlasInfo.symbols.length} symbols"); - return Tuple2(atlasInfo.symbols.values.toList(), atlasInfo.texSize); + var generatedSymbolEntries = atlasInfo.symbols.entries.toList(); + generatedSymbolEntries.sort((a, b) => a.key.compareTo(b.key)); + var generatedSymbols = generatedSymbolEntries.map((e) => e.value).toList(); + return Tuple2(generatedSymbols, atlasInfo.texSize); } Future generateTexture() async { @@ -212,37 +282,63 @@ class FtbData extends ChangeNotifier { showToast("No font override for font $fontId"); throw Exception("No font override for font $fontId"); } - if (!await File(fontOverride.fontPath.value).exists()) { + var hasOverrideFont = await File(fontOverride.fontPath.value).exists(); + if (pendingNewChars.isEmpty && fontOverride.fontPath.value.isNotEmpty && !hasOverrideFont) { showToast("Font path is invalid"); throw Exception("Font path is invalid"); } - // generate cli json args + // font settings var heightScale = fontOverride.heightScale.value.toDouble(); var fontHeight = McdData.availableFonts[fontId]!.fontHeight * heightScale; - var scaleFact = 44 / fontHeight; - CliFontOptions? font = CliFontOptions( + CliFontOptions? overrideFont = hasOverrideFont ? CliFontOptions( fontOverride.fontPath.value, fontHeight.toInt(), - (fontOverride.letXPadding.value * scaleFact).toInt(), - (fontOverride.letYPadding.value * scaleFact).toInt(), - (fontOverride.xOffset.value * scaleFact).toDouble(), - (fontOverride.yOffset.value * scaleFact).toDouble(), + (fontOverride.letXPadding.value * heightScale).toInt(), + (fontOverride.letYPadding.value * heightScale).toInt(), + (fontOverride.xOffset.value * heightScale).toDouble(), + (fontOverride.yOffset.value * heightScale).toDouble(), 1.0, fontOverride.strokeWidth.value.toInt(), - ); + ) : null; + var newFonts = pendingNewChars.map((c) => c.fontPath).toSet(); + if (newFonts.length > 1) + throw Exception("Multiple new fonts"); + CliFontOptions? newFont = newFonts.isNotEmpty ? CliFontOptions( + newFonts.first, + fontHeight.toInt(), + (fontOverride.letXPadding.value * heightScale).toInt(), + (fontOverride.letYPadding.value * heightScale).toInt(), + (fontOverride.xOffset.value * heightScale).toDouble(), + (fontOverride.yOffset.value * heightScale).toDouble(), + 1.0, + fontOverride.strokeWidth.value.toInt(), + ) : null; + // temporary source texture copies + List sourceTextures = await Future.wait(textures.map((tex) async { + var texPath = tex.extractedPngPath!; + var copyPath = "${withoutExtension(texPath)}_copy.png"; + await File(texPath).copy(copyPath); + return copyPath; + })); + // split chars into batches const textureBatchesCount = 4; - var charsPerBatch = (chars.length / textureBatchesCount).ceil(); - List> charsBatches = []; - for (int i = 0; i < chars.length; i += charsPerBatch) { - var batch = chars.sublist(i, min(i + charsPerBatch, chars.length)); + var allChars = [...chars, ...pendingNewChars]; + var charsPerBatch = (allChars.length / textureBatchesCount).ceil(); + List> charsBatches = []; + for (int i = 0; i < allChars.length; i += charsPerBatch) { + var batch = allChars.sublist(i, min(i + charsPerBatch, allChars.length)); charsBatches.add(batch); } - var ddsPaths = List.generate(textureBatchesCount, (i) => join(dirname(wtpPath), "${basename(wtpPath)}_$i.dds")); + var ddsPaths = List.generate(textureBatchesCount, (i) => join(dirname(wtpPath), "${basename(wtpPath)}_extracted", "$i.dds")); var textureBatches = await Future.wait(List.generate( - textureBatchesCount, - (i) => generateTextureBatch(i, charsBatches[i], font, ddsPaths[i]) + textureBatchesCount, + (i) => generateTextureBatch(i, charsBatches[i], overrideFont, newFont, sourceTextures, ddsPaths[i]) )); + // cleanup + await Future.wait(sourceTextures.map((e) => File(e).delete())); + + var wtpSizes = await Future.wait(ddsPaths.map((e) => File(e).length())); // update .wta @@ -283,6 +379,13 @@ class FtbData extends ChangeNotifier { texture.extractedPngPath = "${ddsPaths[i].substring(0, ddsPaths[i].length - 4)}.png"; } + // add new chars + for (var c in pendingNewChars) { + chars.add(c.toDefaultChar()); + } + pendingNewChars.clear(); + + // update char data var batchI = 0; var batchJ = 0; for (var i = 0; i < chars.length; i++) { @@ -299,6 +402,7 @@ class FtbData extends ChangeNotifier { batchJ = 0; } } + chars.sort((a, b) => a.char.compareTo(b.char)); notifyListeners(); @@ -309,7 +413,7 @@ class FtbData extends ChangeNotifier { await generateTexture(); var charsOffset = 0x88 + textures.length * 0x10; - var ftbHeader = FtbFileHeader(_magic, textures.length, 0, chars.length, 0x88, charsOffset, charsOffset); + var ftbHeader = FtbFileHeader(_magic, kerning.value.toInt(), 0, textures.length, 0, chars.length, 0x88, charsOffset, charsOffset); var ftbTextures = textures.map((tex) => FtbFileTexture(0, tex.width, tex.height, @@ -376,6 +480,7 @@ class FtbData extends ChangeNotifier { FtbData copy() { return FtbData( _magic, + kerning.value.toInt(), textures.map((e) => FtbTexture(e.width, e.height)).toList(), chars.map((e) => FtbChar(e.char, e.texId, e.width, e.height, e.x, e.y)).toList(), path, wtaPath, wtpPath, wtaFile, diff --git a/lib/stateManagement/openFiles/types/McdFileData.dart b/lib/stateManagement/openFiles/types/McdFileData.dart index 54f8bef9..a095762f 100644 --- a/lib/stateManagement/openFiles/types/McdFileData.dart +++ b/lib/stateManagement/openFiles/types/McdFileData.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:path/path.dart'; @@ -1029,9 +1028,9 @@ class McdData extends _McdFilePart { ]); } else if (isFallbackOnly || isOverridden) { + var scaleFact = fontOverridesMap[fontId]!.heightScale.value * McdData.fontAtlasResolutionScale.value.toDouble(); var fontHeight = (availableFonts[fontId]!.fontHeight * fontOverridesMap[fontId]!.heightScale.value).toInt(); if (!fonts.containsKey(fontId)) { - var scaleFact = 44 / fontHeight; fonts[fontId] = CliFontOptions( fontOverridesMap[fontId]!.fontPath.value, fontHeight, diff --git a/lib/widgets/filesView/types/fonts/FontsManager.dart b/lib/widgets/filesView/types/fonts/FontsManager.dart index 768ed5be..ae292464 100644 --- a/lib/widgets/filesView/types/fonts/FontsManager.dart +++ b/lib/widgets/filesView/types/fonts/FontsManager.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import '../../../../stateManagement/Property.dart'; import '../../../../stateManagement/openFiles/types/McdFileData.dart'; import '../../../../utils/utils.dart'; import '../../../misc/ChangeNotifierWidget.dart'; @@ -20,8 +21,10 @@ import '../../../theme/customTheme.dart'; class FontsManager extends ChangeNotifierWidget { final McdData? mcd; final int singleFontId; + final bool showResolutionScale; + final List<(String, Prop)> additionalProps; - FontsManager({super.key, this.mcd, this.singleFontId = -1}) : super(notifier: McdData.fontOverrides); + FontsManager({super.key, this.mcd, this.singleFontId = -1, this.showResolutionScale = true, this.additionalProps = const []}) : super(notifier: McdData.fontOverrides); @override State createState() => __McdFontsManagerState(); @@ -124,7 +127,7 @@ class __McdFontsManagerState extends ChangeNotifierState { PrimaryPropTextField( prop: McdData.fontOverrides[i].fontPath, options: const PropTFOptions(hintText: "Font Path", constraints: BoxConstraints.tightFor(height: 30)), - validatorOnChange: (str) => File(str).existsSync() ? null : "File not found", + validatorOnChange: (str) => str.isEmpty || File(str).existsSync() ? null : "File not found", onValid: (str) => McdData.fontOverrides[i].fontPath.value = str, ), SmallButton( @@ -197,20 +200,29 @@ class __McdFontsManagerState extends ChangeNotifierState { ), Row( children: [ - const Text("Letter Spacing: "), + const Text("Letter Padding: "), makePropEditor( McdData.fontAtlasLetterSpacing, - const PropTFOptions(hintText: "Letter Spacing", constraints: BoxConstraints.tightFor(height: 30, width: 50)), - ), - ], - ), - Row( - children: [ - const Text("Resolution Scale: "), - makePropEditor( - McdData.fontAtlasResolutionScale, - const PropTFOptions(hintText: "Resolution Scale", constraints: BoxConstraints.tightFor(height: 30, width: 50)), + const PropTFOptions(hintText: "Letter Padding", constraints: BoxConstraints.tightFor(height: 30, width: 50)), ), + if (widget.showResolutionScale) + ...[ + const SizedBox(width: 20), + const Text("Resolution Scale: "), + makePropEditor( + McdData.fontAtlasResolutionScale, + const PropTFOptions(hintText: "Resolution Scale", constraints: BoxConstraints.tightFor(height: 30, width: 50)), + ), + ], + for (var (label, prop) in widget.additionalProps) + ...[ + const SizedBox(width: 20), + Text("$label: "), + makePropEditor( + prop, + PropTFOptions(hintText: label, constraints: const BoxConstraints.tightFor(height: 30, width: 50)), + ), + ], ], ), if (widget.mcd != null) diff --git a/lib/widgets/filesView/types/fonts/fontOverridesApply.dart b/lib/widgets/filesView/types/fonts/fontOverridesApply.dart index 99eae690..76ef7474 100644 --- a/lib/widgets/filesView/types/fonts/fontOverridesApply.dart +++ b/lib/widgets/filesView/types/fonts/fontOverridesApply.dart @@ -6,9 +6,12 @@ import '../../../../stateManagement/events/statusInfo.dart'; import '../../../../stateManagement/openFiles/openFilesManager.dart'; import '../../../../stateManagement/openFiles/types/FtbFileData.dart'; import '../../../../stateManagement/openFiles/types/McdFileData.dart'; +import '../../../theme/customTheme.dart'; class FontOverridesApplyButton extends StatelessWidget { - const FontOverridesApplyButton({super.key}); + final bool showText; + + const FontOverridesApplyButton({super.key, this.showText = false}); Future applyAllFontOverrides() async { isLoadingStatus.pushIsLoading(); @@ -30,10 +33,26 @@ class FontOverridesApplyButton extends StatelessWidget { @override Widget build(BuildContext context) { - return IconButton( - onPressed: applyAllFontOverrides, - tooltip: "Apply all font overrides & export all open mcd/ftb files", - icon: const Icon(Icons.save) - ); + if (showText) { + return Tooltip( + message: "Apply all font overrides & export all open mcd/ftb files", + child: TextButton.icon( + onPressed: applyAllFontOverrides, + label: const Text("Apply new font"), + icon: const Icon(Icons.save), + style: ButtonStyle( + foregroundColor: WidgetStateProperty.all(getTheme(context).textColor), + padding: WidgetStateProperty.all(const EdgeInsets.all(14)) + ), + ), + ); + } + else { + return IconButton( + onPressed: applyAllFontOverrides, + tooltip: "Apply all font overrides & export all open mcd/ftb files", + icon: const Icon(Icons.save) + ); + } } } diff --git a/lib/widgets/filesView/types/fonts/ftbEditor.dart b/lib/widgets/filesView/types/fonts/ftbEditor.dart index 1b0f5847..20913fda 100644 --- a/lib/widgets/filesView/types/fonts/ftbEditor.dart +++ b/lib/widgets/filesView/types/fonts/ftbEditor.dart @@ -1,10 +1,12 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import '../../../../stateManagement/openFiles/openFileTypes.dart'; import '../../../../stateManagement/openFiles/types/FtbFileData.dart'; import '../../../../stateManagement/openFiles/types/McdFileData.dart'; import '../../../misc/ChangeNotifierWidget.dart'; +import '../../../theme/customTheme.dart'; import 'FontsManager.dart'; import 'McdFontDebugger.dart'; import 'fontOverridesApply.dart'; @@ -27,6 +29,19 @@ class _FtbEditorState extends ChangeNotifierState { super.initState(); } + Future addNewFontChars() async { + var filesResult = await FilePicker.platform.pickFiles( + dialogTitle: "Select font file", + allowedExtensions: ["ttf", "otf"], + type: FileType.custom, + allowMultiple: false, + ); + if (filesResult == null || filesResult.files.isEmpty) + return; + var fontFile = filesResult.files.first; + await widget.file.addCharsFromFront(fontFile.path!); + } + @override Widget build(BuildContext context) { if (widget.file.loadingState.value != LoadingState.loaded) { @@ -45,23 +60,34 @@ class _FtbEditorState extends ChangeNotifierState { child: Column( children: [ const SizedBox(height: 35), - FontsManager(singleFontId: widget.file.ftbData!.fontId), + FontsManager( + singleFontId: widget.file.ftbData!.fontId, + showResolutionScale: false, + additionalProps: [ + ("Global kerning", widget.file.ftbData!.kerning) + ], + ), + Row( + children: [ + const FontOverridesApplyButton(showText: true), + const SizedBox(width: 30), + TextButton.icon( + onPressed: addNewFontChars, + style: ButtonStyle( + foregroundColor: WidgetStateProperty.all(getTheme(context).textColor), + padding: WidgetStateProperty.all(const EdgeInsets.all(14)) + ), + icon: const Icon(Icons.note_add), + label: const Text("Add new characters from TTF/OTF file"), + ), + ], + ), Expanded( child: Row( children: [ - Column( - children: [ - const FontOverridesApplyButton(), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: IconButton( - icon: const Icon(Icons.arrow_left), - onPressed: activeTexture > 0 ? () => setState(() => activeTexture--) : null, - ), - ), - ), - ], + IconButton( + icon: const Icon(Icons.arrow_left), + onPressed: activeTexture > 0 ? () => setState(() => activeTexture--) : null, ), Expanded( child: IndexedStack(