mirror of
https://github.com/Xiao-MoMi/Custom-Nameplates.git
synced 2025-12-28 19:29:17 +00:00
TTF
This commit is contained in:
@@ -23,6 +23,7 @@ dependencies {
|
||||
compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}")
|
||||
// COMMONS IO
|
||||
compileOnly("commons-io:commons-io:${rootProject.properties["commons_io_version"]}")
|
||||
// lwjgl
|
||||
implementation("org.lwjgl:lwjgl-freetype:3.3.4")
|
||||
// TTF
|
||||
compileOnly("org.lwjgl:lwjgl-freetype:${rootProject.properties["lwjgl_version"]}")
|
||||
compileOnly("org.lwjgl:lwjgl:${rootProject.properties["lwjgl_version"]}")
|
||||
}
|
||||
@@ -49,13 +49,23 @@ import net.momirealms.customnameplates.api.placeholder.Placeholder;
|
||||
import net.momirealms.customnameplates.api.placeholder.PlayerPlaceholder;
|
||||
import net.momirealms.customnameplates.api.placeholder.SharedPlaceholder;
|
||||
import net.momirealms.customnameplates.api.util.CharacterUtils;
|
||||
import net.momirealms.customnameplates.backend.util.FreeTypeUtils;
|
||||
import net.momirealms.customnameplates.common.util.Tuple;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.freetype.FT_Face;
|
||||
import org.lwjgl.util.freetype.FT_GlyphSlot;
|
||||
import org.lwjgl.util.freetype.FT_Vector;
|
||||
import org.lwjgl.util.freetype.FreeType;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -768,16 +778,115 @@ public class AdvanceManagerImpl implements AdvanceManager {
|
||||
|
||||
File ttfCache = new File(plugin.getDataDirectory().toFile(), "tmp" + File.separator + id + ".tmp");
|
||||
if (!ttfCache.exists()) {
|
||||
File ttfFile = new File(plugin.getDataDirectory().toFile(), "font" + File.separator + path);
|
||||
if (!ttfFile.exists()) {
|
||||
plugin.getPluginLogger().warn(ttfFile.getAbsolutePath() + " not found");
|
||||
File ttf = new File(plugin.getDataDirectory().toFile(), "font" + File.separator + path);
|
||||
if (!ttf.exists()) {
|
||||
plugin.getPluginLogger().warn(ttf.getAbsolutePath() + " not found");
|
||||
return;
|
||||
}
|
||||
if (!ttfFile.getName().endsWith(".ttf")) {
|
||||
plugin.getPluginLogger().warn(ttfFile.getAbsolutePath() + " is not a .ttf");
|
||||
if (!ttf.getName().endsWith(".ttf")) {
|
||||
plugin.getPluginLogger().warn(ttf.getAbsolutePath() + " is not a .ttf");
|
||||
return;
|
||||
}
|
||||
try (InputStream inputStream = new FileInputStream(ttf)) {
|
||||
ByteBuffer byteBuffer = null;
|
||||
FT_Face fT_Face = null;
|
||||
try {
|
||||
ttfCache.getParentFile().mkdirs();
|
||||
ttfCache.createNewFile();
|
||||
YamlDocument yml = plugin.getConfigManager().loadData(ttfCache);
|
||||
byteBuffer = FreeTypeUtils.readResource(inputStream);
|
||||
byteBuffer.flip();
|
||||
synchronized(FreeTypeUtils.LOCK) {
|
||||
MemoryStack ms1 = MemoryStack.stackPush();
|
||||
try {
|
||||
PointerBuffer pointerBuffer = ms1.mallocPointer(1);
|
||||
FreeTypeUtils.checkFatalError(FreeType.FT_New_Memory_Face(FreeTypeUtils.initialize(), byteBuffer, 0L, pointerBuffer), "Initializing font face");
|
||||
fT_Face = FT_Face.create(pointerBuffer.get());
|
||||
} catch (Throwable t1) {
|
||||
try {
|
||||
ms1.close();
|
||||
} catch (Throwable t2) {
|
||||
t1.addSuppressed(t2);
|
||||
}
|
||||
throw t1;
|
||||
}
|
||||
ms1.close();
|
||||
|
||||
String string = FreeType.FT_Get_Font_Format(fT_Face);
|
||||
if (!"TrueType".equals(string)) {
|
||||
throw new IOException("Font is not in TTF format, was " + string);
|
||||
}
|
||||
|
||||
FreeTypeUtils.checkFatalError(FreeType.FT_Select_Charmap(fT_Face, FreeType.FT_ENCODING_UNICODE), "Find unicode charmap");
|
||||
Set<Integer> codePoints = new HashSet<>(1_000);
|
||||
|
||||
int pixelWidth = Math.round(size * oversample);
|
||||
int pixelHeight = Math.round(size * oversample);
|
||||
FreeType.FT_Set_Pixel_Sizes(fT_Face, pixelWidth, pixelHeight);
|
||||
|
||||
MemoryStack ms2 = MemoryStack.stackPush();
|
||||
try {
|
||||
FT_Vector fT_Vector = FreeTypeUtils.setShift(FT_Vector.malloc(ms2), 0, 0);
|
||||
FreeType.FT_Set_Transform(fT_Face, null, fT_Vector);
|
||||
} catch (Throwable t1) {
|
||||
try {
|
||||
ms2.close();
|
||||
} catch (Throwable t2) {
|
||||
t1.addSuppressed(t2);
|
||||
}
|
||||
throw t1;
|
||||
}
|
||||
ms2.close();
|
||||
|
||||
MemoryStack ms3 = MemoryStack.stackPush();
|
||||
try {
|
||||
IntBuffer intBuffer = ms3.mallocInt(1);
|
||||
for (long l = FreeType.FT_Get_First_Char(fT_Face, intBuffer); intBuffer.get(0) != 0; l = FreeType.FT_Get_Next_Char(fT_Face, l, intBuffer)) {
|
||||
codePoints.add((int) l);
|
||||
}
|
||||
} catch (Throwable t1) {
|
||||
try {
|
||||
ms3.close();
|
||||
} catch (Throwable t2) {
|
||||
t1.addSuppressed(t2);
|
||||
}
|
||||
throw t1;
|
||||
}
|
||||
ms3.close();
|
||||
|
||||
for (int skippedCodePoint : skippCodePoints) {
|
||||
codePoints.remove(skippedCodePoint);
|
||||
}
|
||||
for (int codePoint : codePoints) {
|
||||
int i = FreeType.FT_Get_Char_Index(fT_Face, codePoint);
|
||||
if (i != 0) {
|
||||
FreeTypeUtils.checkFatalError(FreeType.FT_Load_Glyph(fT_Face, i, 4194312), "Loading glyph");
|
||||
FT_GlyphSlot fT_GlyphSlot = Objects.requireNonNull(fT_Face.glyph(), "Glyph not initialized");
|
||||
float advance = FreeTypeUtils.getAdvance(fT_GlyphSlot.advance());
|
||||
char[] theCharacter = Character.toChars(codePoint);
|
||||
String unicode = CharacterUtils.char2Unicode(theCharacter);
|
||||
yml.set(unicode, (advance) / oversample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yml.save(ttfCache);
|
||||
} catch (Exception e) {
|
||||
synchronized(FreeTypeUtils.LOCK) {
|
||||
if (fT_Face != null) {
|
||||
FreeType.FT_Done_Face(fT_Face);
|
||||
}
|
||||
}
|
||||
MemoryUtil.memFree(byteBuffer);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// free resources
|
||||
MemoryUtil.memFree(byteBuffer);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
registerCharacterFontData(id, ttfCache, (properties) -> {
|
||||
@@ -916,6 +1025,7 @@ public class AdvanceManagerImpl implements AdvanceManager {
|
||||
public void disable() {
|
||||
this.unload();
|
||||
this.charFontWidthDataMap.clear();
|
||||
FreeTypeUtils.release();
|
||||
}
|
||||
|
||||
private void loadTemplates() {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customnameplates.backend.util;
|
||||
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.freetype.FT_Vector;
|
||||
import org.lwjgl.util.freetype.FreeType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
public class FreeTypeUtils {
|
||||
|
||||
public static final Object LOCK = new Object();
|
||||
private static long freeType = 0L;
|
||||
|
||||
public FreeTypeUtils() {
|
||||
}
|
||||
|
||||
public static long initialize() {
|
||||
synchronized(LOCK) {
|
||||
if (freeType == 0L) {
|
||||
MemoryStack memoryStack = MemoryStack.stackPush();
|
||||
try {
|
||||
PointerBuffer pointerBuffer = memoryStack.mallocPointer(1);
|
||||
checkFatalError(FreeType.FT_Init_FreeType(pointerBuffer), "Initializing FreeType library");
|
||||
freeType = pointerBuffer.get();
|
||||
} catch (Throwable t1) {
|
||||
try {
|
||||
memoryStack.close();
|
||||
} catch (Throwable t2) {
|
||||
t1.addSuppressed(t2);
|
||||
}
|
||||
throw t1;
|
||||
}
|
||||
memoryStack.close();
|
||||
}
|
||||
return freeType;
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkFatalError(int code, String description) {
|
||||
if (code != 0) {
|
||||
String var10002 = getErrorMessage(code);
|
||||
throw new IllegalStateException("FreeType error: " + var10002 + " (" + description + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getErrorMessage(int code) {
|
||||
String string = FreeType.FT_Error_String(code);
|
||||
return string != null ? string : "Unrecognized error: 0x" + Integer.toHexString(code);
|
||||
}
|
||||
|
||||
public static FT_Vector setShift(FT_Vector vec, float x, float y) {
|
||||
long xShift = Math.round(x * 64.0F);
|
||||
long yShift = Math.round(y * 64.0F);
|
||||
return vec.set(xShift, yShift);
|
||||
}
|
||||
|
||||
public static float getAdvance(FT_Vector vec) {
|
||||
return (float) vec.x() / 64.0F;
|
||||
}
|
||||
|
||||
public static void release() {
|
||||
synchronized(LOCK) {
|
||||
if (freeType != 0L) {
|
||||
FreeType.FT_Done_Library(freeType);
|
||||
freeType = 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuffer readResource(InputStream inputStream) throws IOException {
|
||||
ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream);
|
||||
if (readableByteChannel instanceof SeekableByteChannel seekableByteChannel) {
|
||||
return readResource(readableByteChannel, (int)seekableByteChannel.size() + 1);
|
||||
} else {
|
||||
return readResource(readableByteChannel, 8192);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer readResource(ReadableByteChannel channel, int bufSize) throws IOException {
|
||||
ByteBuffer byteBuffer = MemoryUtil.memAlloc(bufSize);
|
||||
try {
|
||||
while(channel.read(byteBuffer) != -1) {
|
||||
if (!byteBuffer.hasRemaining()) {
|
||||
byteBuffer = MemoryUtil.memRealloc(byteBuffer, byteBuffer.capacity() * 2);
|
||||
}
|
||||
}
|
||||
return byteBuffer;
|
||||
} catch (IOException e) {
|
||||
MemoryUtil.memFree(byteBuffer);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user