mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Initial commit for modern bitmap and character providers
This commit is contained in:
1 parent
7017786a7c
commit
5a9eb8dc55
9 files changed
+700
-75
No files matched your search
@@ -10,6 +10,7 @@ import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LE
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.compat.etfuturum.EtFuturumFontCompat;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
@@ -702,30 +703,79 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
|
||||
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
|
||||
if (replacement != 0) {
|
||||
if (FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement)
|
||||
) {
|
||||
chr = replacement;
|
||||
final boolean surrogatePair = EtFuturumFontCompat.MODERN_FONT_ENABLED && !unicodeFlag
|
||||
&& Character.isHighSurrogate(chr) && (charIdx + 1) < stringEnd
|
||||
&& Character.isLowSurrogate(string.charAt(charIdx + 1));
|
||||
final int codepoint = surrogatePair ? Character.toCodePoint(chr, string.charAt(charIdx + 1)) : chr;
|
||||
final SupplementaryGlyphSource supGlyph = surrogatePair ? supplementarySource(codepoint) : null;
|
||||
final boolean supplementary = supGlyph != null;
|
||||
|
||||
if (!supplementary) {
|
||||
if (EtFuturumFontCompat.MODERN_FONT_ENABLED && Character.isSurrogate(chr)) {
|
||||
// Substitute U+FFFD for a bad malformed glyph
|
||||
final boolean pairedHigh = Character.isHighSurrogate(chr) && charIdx + 1 < stringEnd
|
||||
&& Character.isLowSurrogate(string.charAt(charIdx + 1));
|
||||
final boolean pairedLow = Character.isLowSurrogate(chr) && charIdx > stringOffset
|
||||
&& Character.isHighSurrogate(string.charAt(charIdx - 1));
|
||||
if (!pairedHigh && !pairedLow) { warnIsolatedSurrogate(chr, string); }
|
||||
chr = REPLACEMENT_GLYPH;
|
||||
}
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
|
||||
if (replacement != 0) {
|
||||
if (FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement)
|
||||
) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curRandom) {
|
||||
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
|
||||
}
|
||||
}
|
||||
|
||||
if (curRandom) {
|
||||
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
|
||||
float uStart, vStart, xAdvance, glyphW, uSz, vSz, shadowOffset, provYScale, provBaselineShift;
|
||||
ResourceLocation texture;
|
||||
boolean unifontGlyph;
|
||||
if (supplementary) {
|
||||
unifontGlyph = supGlyph.isUnifont();
|
||||
uStart = supGlyph.getUStartCp(codepoint);
|
||||
vStart = supGlyph.getVStartCp(codepoint);
|
||||
xAdvance = supGlyph.getXAdvanceCp(codepoint) * glyphScaleX;
|
||||
if (unifontGlyph) { xAdvance = (int) xAdvance; }
|
||||
glyphW = supGlyph.getGlyphWCp(codepoint) * glyphScaleX;
|
||||
uSz = supGlyph.getUSizeCp(codepoint);
|
||||
vSz = supGlyph.getVSizeCp(codepoint);
|
||||
shadowOffset = supGlyph.getShadowOffset();
|
||||
provYScale = supGlyph.getYScaleMultiplier();
|
||||
provBaselineShift = supGlyph.getBaselineShift();
|
||||
texture = supGlyph.getTextureCp(codepoint);
|
||||
} else {
|
||||
final FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
unifontGlyph = fontProvider == FontProviderUnicode.get();
|
||||
uStart = fontProvider.getUStart(chr);
|
||||
vStart = fontProvider.getVStart(chr);
|
||||
xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
if (unifontGlyph) { xAdvance = (int) xAdvance; }
|
||||
glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
uSz = fontProvider.getUSize(chr);
|
||||
vSz = fontProvider.getVSize(chr);
|
||||
shadowOffset = fontProvider.getShadowOffset();
|
||||
provYScale = fontProvider.getYScaleMultiplier();
|
||||
provBaselineShift = fontProvider.getBaselineShift();
|
||||
texture = fontProvider.getTexture(chr);
|
||||
}
|
||||
|
||||
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * provYScale / 2)
|
||||
+ provBaselineShift * glyphScaleY;
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * provYScale;
|
||||
|
||||
visibleCharIndex++;
|
||||
|
||||
// Check ASCII space, NBSP, NNBSP
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
if (!supplementary && (chr == ' ' || chr == '\u00A0' || chr == '\u202F')) {
|
||||
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
|
||||
continue;
|
||||
}
|
||||
@@ -747,17 +797,9 @@ public class BatchingFontRenderer {
|
||||
gradientCharIndex++;
|
||||
}
|
||||
|
||||
final float uStart = fontProvider.getUStart(chr);
|
||||
final float vStart = fontProvider.getVStart(chr);
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
final float uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final ResourceLocation texture = fontProvider.getTexture(chr);
|
||||
final int idxId = idxWriterIndex;
|
||||
|
||||
// Wave: Y offset via sine wave
|
||||
@@ -802,7 +844,7 @@ public class BatchingFontRenderer {
|
||||
if (drawShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
|
||||
if (curBold) { charCount += boldCopies; }
|
||||
final int vtxCount = 4 * charCount;
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, unifontGlyph);
|
||||
|
||||
curX += (xAdvance + (curBold ? 1.0f : 0.0f)) + getGlyphSpacing();
|
||||
if (bookMode) { curX = (int) curX; }
|
||||
@@ -821,6 +863,7 @@ public class BatchingFontRenderer {
|
||||
pushDrawCmd(stIdx, 6, null, false);
|
||||
strikethroughStartX = strikethroughEndX;
|
||||
}
|
||||
if (supplementary) { charIdx++; }
|
||||
}
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
@@ -845,9 +888,64 @@ public class BatchingFontRenderer {
|
||||
return curX + (enableShadow ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
public int getStringWidthBatched(String text) {
|
||||
if (text == null) return 0;
|
||||
text = ColorCodeUtils.convertAmpersandToSectionX(text);
|
||||
final boolean unicodeFlag = underlying.getUnicodeFlag();
|
||||
final float glyphScaleX = getGlyphScaleX();
|
||||
final int len = text.length();
|
||||
boolean bold = false;
|
||||
int width = 0;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
final char c0 = text.charAt(i);
|
||||
|
||||
if (EtFuturumFontCompat.MODERN_FONT_ENABLED && !unicodeFlag && Character.isHighSurrogate(c0)
|
||||
&& i + 1 < len && Character.isLowSurrogate(text.charAt(i + 1))) {
|
||||
final int cp = Character.toCodePoint(c0, text.charAt(i + 1));
|
||||
final SupplementaryGlyphSource src = supplementarySource(cp);
|
||||
if (src != null) {
|
||||
final int w = (int) (src.getXAdvanceCp(cp) * glyphScaleX);
|
||||
width += w;
|
||||
if (bold && w > 0) width += 1;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int k = (int) getCharWidthFine(c0);
|
||||
if (k < 0 && i < len - 1) {
|
||||
// §-format code
|
||||
i++;
|
||||
final char code = text.charAt(i);
|
||||
if (code == 'l' || code == 'L') {
|
||||
bold = true;
|
||||
} else if (code == 'r' || code == 'R') {
|
||||
bold = false;
|
||||
}
|
||||
k = 0;
|
||||
}
|
||||
width += k;
|
||||
if (bold && k > 0) width += 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
private static final char REPLACEMENT_GLYPH = '\uFFFD';
|
||||
private static boolean warnedIsolatedSurrogate;
|
||||
|
||||
private static void warnIsolatedSurrogate(char chr, CharSequence string) {
|
||||
if (warnedIsolatedSurrogate) { return; }
|
||||
warnedIsolatedSurrogate = true;
|
||||
FontStrategist.LOGGER.warn(
|
||||
"Split emoji/extended char U+{} in \"{}\". A GUI split a supplementary character into separate Java chars during cursor/text ops. Rendering U+FFFD instead.",
|
||||
Integer.toHexString(chr).toUpperCase(), string, new Throwable("isolated-surrogate draw site"));
|
||||
}
|
||||
|
||||
public float getCharWidthFine(char chr) {
|
||||
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
|
||||
if (chr == FORMATTING_CHAR) { return -1; }
|
||||
if (EtFuturumFontCompat.MODERN_FONT_ENABLED && Character.isSurrogate(chr)) { chr = REPLACEMENT_GLYPH; }
|
||||
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
@@ -858,6 +956,20 @@ public class BatchingFontRenderer {
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
public int supplementaryAdvance(int codepoint) {
|
||||
if (!EtFuturumFontCompat.MODERN_FONT_ENABLED) return -1;
|
||||
final SupplementaryGlyphSource src = supplementarySource(codepoint);
|
||||
return src != null ? (int) (src.getXAdvanceCp(codepoint) * getGlyphScaleX()) : -1;
|
||||
}
|
||||
|
||||
|
||||
private SupplementaryGlyphSource supplementarySource(int codepoint) {
|
||||
final FontProviderBitmap bitmap = (!bookMode && !isSplash) ? FontStrategist.findSupplementaryBitmap(codepoint) : null;
|
||||
if (bitmap != null) return bitmap;
|
||||
final FontProviderUnicode unicode = FontProviderUnicode.get();
|
||||
return unicode.hasCodepoint(codepoint) ? unicode : null;
|
||||
}
|
||||
|
||||
public void overrideBlendFunc(int srcRgb, int dstRgb) {
|
||||
blendSrcRGB = srcRgb;
|
||||
blendDstRGB = dstRgb;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public record BitmapGlyph(ResourceLocation texture, float uStart, float vStart, float uSize, float vSize, float glyphW,
|
||||
float advance) {
|
||||
|
||||
}
|
||||
@@ -18,4 +18,6 @@ public interface FontProvider {
|
||||
float getShadowOffset();
|
||||
ResourceLocation getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
|
||||
default float getBaselineShift() { return 0.0f; }
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A backported modern hand-drawn bitmap font provider.
|
||||
* Et Futurum: Requium provides the resources.
|
||||
*/
|
||||
public final class FontProviderBitmap implements SupplementaryGlyphSource {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final ResourceLocation HANDDRAWN_DEFINITION = new ResourceLocation("etfuturum", "font/handdrawn.json");
|
||||
|
||||
private static final float ASCII_CELL = 8.0f;
|
||||
private static final float ASCII_ASCENT = 7.0f;
|
||||
|
||||
private final Int2ObjectOpenHashMap<BitmapGlyph> glyphs = new Int2ObjectOpenHashMap<>();
|
||||
private final float yScale;
|
||||
private final float baselineShift;
|
||||
|
||||
private FontProviderBitmap(int renderHeight, int ascent) {
|
||||
this.yScale = renderHeight / ASCII_CELL;
|
||||
this.baselineShift = (ASCII_ASCENT - 4.0f) + 4.0f * this.yScale - ascent;
|
||||
}
|
||||
|
||||
public static List<FontProviderBitmap> loadProviders() {
|
||||
final List<FontProviderBitmap> providers = new ArrayList<>();
|
||||
final IResourceManager rm = Minecraft.getMinecraft().getResourceManager();
|
||||
try (InputStreamReader reader = new InputStreamReader(
|
||||
rm.getResource(HANDDRAWN_DEFINITION).getInputStream(), StandardCharsets.UTF_8)) {
|
||||
final JsonObject root = new JsonParser().parse(reader).getAsJsonObject();
|
||||
final JsonArray defs = root.getAsJsonArray("providers");
|
||||
int glyphCount = 0;
|
||||
for (int i = 0; i < defs.size(); i++) {
|
||||
final FontProviderBitmap p = fromDefinition(defs.get(i).getAsJsonObject(), rm);
|
||||
if (p != null && !p.glyphs.isEmpty()) {
|
||||
providers.add(p);
|
||||
glyphCount += p.glyphs.size();
|
||||
}
|
||||
}
|
||||
if (!providers.isEmpty()) {
|
||||
LOGGER.info("Loaded {} modern hand-drawn font glyph(s) across {} provider(s)", glyphCount, providers.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Modern hand-drawn font sheets not loaded ({})", e.toString());
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap fromDefinition(JsonObject def, IResourceManager rm) {
|
||||
try {
|
||||
final String file = def.get("file").getAsString();
|
||||
final int height = def.has("height") ? def.get("height").getAsInt() : (int) ASCII_CELL;
|
||||
final int ascent = def.get("ascent").getAsInt();
|
||||
final JsonArray charRows = def.getAsJsonArray("chars");
|
||||
|
||||
// "namespace:font/x.png" -> "namespace:textures/font/x.png"
|
||||
final int colon = file.indexOf(':');
|
||||
final String namespace = colon >= 0 ? file.substring(0, colon) : "minecraft";
|
||||
final String path = colon >= 0 ? file.substring(colon + 1) : file;
|
||||
final ResourceLocation texture = new ResourceLocation(namespace, "textures/" + path);
|
||||
|
||||
final BufferedImage image = ImageIO.read(rm.getResource(texture).getInputStream());
|
||||
final int texW = image.getWidth();
|
||||
final int texH = image.getHeight();
|
||||
final int rows = charRows.size();
|
||||
final String firstRow = charRows.get(0).getAsString();
|
||||
final int cols = firstRow.codePointCount(0, firstRow.length());
|
||||
final int cellW = texW / cols;
|
||||
final int cellH = texH / rows;
|
||||
final float scale = (float) height / cellH;
|
||||
|
||||
final FontProviderBitmap provider = new FontProviderBitmap(height, ascent);
|
||||
for (int row = 0; row < rows; row++) {
|
||||
final String line = charRows.get(row).getAsString();
|
||||
int col = 0;
|
||||
for (int offset = 0; offset < line.length(); ) {
|
||||
final int cp = line.codePointAt(offset);
|
||||
offset += Character.charCount(cp);
|
||||
if (cp != 0 && !provider.glyphs.containsKey(cp)) {
|
||||
final int cellX = col * cellW;
|
||||
final int cellY = row * cellH;
|
||||
final int ink = inkWidth(image, cellX, cellY, cellW, cellH);
|
||||
final float advance = (int) (0.5F + ink * scale) + 1.0f;
|
||||
final float glyphW = cellW * scale + 1.0f;
|
||||
provider.glyphs.put(cp, new BitmapGlyph(texture,
|
||||
(float) cellX / texW, (float) cellY / texH, (float) cellW / texW, (float) cellH / texH,
|
||||
glyphW, advance));
|
||||
}
|
||||
col++;
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Skipping a hand-drawn font provider ({})", e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Rightmost inked column + 1 within a cell. */
|
||||
private static int inkWidth(BufferedImage image, int cellX, int cellY, int cellW, int cellH) {
|
||||
for (int x = cellW - 1; x >= 0; x--) {
|
||||
for (int y = 0; y < cellH; y++) {
|
||||
if ((image.getRGB(cellX + x, cellY + y) >>> 24) != 0) {
|
||||
return x + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int lastCp = -1;
|
||||
private BitmapGlyph lastGlyph;
|
||||
|
||||
private BitmapGlyph glyph(int cp) {
|
||||
if (cp == this.lastCp && this.lastGlyph != null) {
|
||||
return this.lastGlyph;
|
||||
}
|
||||
final BitmapGlyph g = this.glyphs.get(cp);
|
||||
this.lastCp = cp;
|
||||
this.lastGlyph = g;
|
||||
return g;
|
||||
}
|
||||
|
||||
public boolean hasGlyph(int cp) { return this.glyphs.containsKey(cp); }
|
||||
|
||||
@Override
|
||||
public float getUStartCp(int cp) { return glyph(cp).uStart(); }
|
||||
|
||||
@Override
|
||||
public float getVStartCp(int cp) { return glyph(cp).vStart(); }
|
||||
|
||||
@Override
|
||||
public float getXAdvanceCp(int cp) { return glyph(cp).advance(); }
|
||||
|
||||
@Override
|
||||
public float getGlyphWCp(int cp) { return glyph(cp).glyphW(); }
|
||||
|
||||
@Override
|
||||
public float getUSizeCp(int cp) { return glyph(cp).uSize(); }
|
||||
|
||||
@Override
|
||||
public float getVSizeCp(int cp) { return glyph(cp).vSize(); }
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureCp(int cp) { return glyph(cp).texture(); }
|
||||
|
||||
@Override
|
||||
public boolean isUnifont() { return false; }
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) { return hasGlyph(chr); }
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) { return getUStartCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) { return getVStartCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) { return getXAdvanceCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) { return getGlyphWCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) { return getUSizeCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) { return getVSizeCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) { return getTextureCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return this.yScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBaselineShift() {
|
||||
return this.baselineShift;
|
||||
}
|
||||
}
|
||||
@@ -3,43 +3,104 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
public final class FontProviderUnicode implements SupplementaryGlyphSource {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private FontProviderUnicode() {
|
||||
try {
|
||||
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputstream.read(this.glyphWidth);
|
||||
}
|
||||
|
||||
private void loadBmpWidths() {
|
||||
try (InputStream in = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream()) {
|
||||
readWidths(in, this.glyphWidth);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to load font/glyph_sizes.bin; BMP glyph widths default to 0", e);
|
||||
}
|
||||
catch (IOException ioexception) {
|
||||
throw new RuntimeException(ioexception);
|
||||
}
|
||||
|
||||
public static void readWidths(InputStream in, byte[] dest) throws IOException {
|
||||
int off = 0, n;
|
||||
while (off < dest.length && (n = in.read(dest, off, dest.length - off)) != -1) {
|
||||
off += n;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
|
||||
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
|
||||
private static final int SUPPLEMENTARY_BASE = 0x10000;
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[0x1100];
|
||||
private final byte[] glyphWidth = new byte[65536];
|
||||
private boolean bmpLoaded;
|
||||
|
||||
private byte[] hiGlyphWidth;
|
||||
private boolean hiLoaded;
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
if (page >= 0 && page < unicodePageLocations.length) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup != null) return lookup;
|
||||
// %02x is a minimum width, so supplementary pages still format correctly
|
||||
final ResourceLocation rl = new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
return new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", page));
|
||||
}
|
||||
|
||||
private int widthByte(int codepoint) {
|
||||
if (codepoint < SUPPLEMENTARY_BASE) {
|
||||
ensureBmpWidths();
|
||||
return this.glyphWidth[codepoint];
|
||||
}
|
||||
ensureHiWidths();
|
||||
final int idx = codepoint - SUPPLEMENTARY_BASE;
|
||||
return idx < this.hiGlyphWidth.length ? this.hiGlyphWidth[idx] : 0;
|
||||
}
|
||||
|
||||
private void ensureBmpWidths() {
|
||||
if (this.bmpLoaded) return;
|
||||
this.bmpLoaded = true;
|
||||
loadBmpWidths();
|
||||
}
|
||||
|
||||
private void ensureHiWidths() {
|
||||
if (this.hiLoaded) return;
|
||||
this.hiLoaded = true;
|
||||
this.hiGlyphWidth = new byte[0];
|
||||
try (InputStream in = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes_hi.bin")).getInputStream()) {
|
||||
java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();
|
||||
byte[] chunk = new byte[8192];
|
||||
int n;
|
||||
while ((n = in.read(chunk)) != -1) buf.write(chunk, 0, n);
|
||||
this.hiGlyphWidth = buf.toByteArray();
|
||||
} catch (IOException e) {
|
||||
LOGGER.debug("font/glyph_sizes_hi.bin not loaded ({}); supplementary glyph widths default to 0", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.bmpLoaded = false;
|
||||
this.hiLoaded = false;
|
||||
this.hiGlyphWidth = null;
|
||||
}
|
||||
|
||||
public boolean hasCodepoint(int codepoint) {
|
||||
return widthByte(codepoint) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnifont() { return true; }
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return true;
|
||||
@@ -50,46 +111,61 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
|
||||
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
public float getUStartCp(int cp) {
|
||||
final float startColumnF = (float) ((widthByte(cp) >>> 4) & 15);
|
||||
return ((float) ((cp & 15) * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
|
||||
public float getVStartCp(int cp) {
|
||||
return ((float) (((cp & 255) >> 4) * 16) + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
return (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
public float getXAdvanceCp(int cp) {
|
||||
final int w = widthByte(cp);
|
||||
final int startColumn = (w >>> 4) & 15;
|
||||
final int endColumn = w & 15;
|
||||
return ((endColumn + 1) - startColumn) / 2.0F + 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
public float getGlyphWCp(int cp) {
|
||||
final int w = widthByte(cp);
|
||||
final int startColumn = (w >>> 4) & 15;
|
||||
final int endColumn = w & 15;
|
||||
final float chrWidth = (endColumn + 1) - startColumn - 0.02F;
|
||||
return chrWidth / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
public float getUSizeCp(int cp) {
|
||||
final int w = widthByte(cp);
|
||||
final int startColumn = (w >>> 4) & 15;
|
||||
final int endColumn = w & 15;
|
||||
final float chrWidth = (endColumn + 1) - startColumn - 0.02F;
|
||||
return (chrWidth - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
public float getVSizeCp(int cp) {
|
||||
return getVSize((char) 0);
|
||||
}
|
||||
|
||||
public ResourceLocation getTextureCp(int cp) {
|
||||
return getUnicodePageLocation(cp >>> 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) { return getUStartCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) { return getVStartCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) { return getXAdvanceCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) { return getGlyphWCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) { return getUSizeCp(chr); }
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return (16.0f - 0.42f) / 256.0f;
|
||||
@@ -102,8 +178,7 @@ public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
return getUnicodePageLocation(uniPage);
|
||||
return getTextureCp(chr);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.compat.etfuturum.EtFuturumFontCompat;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
@@ -8,6 +9,7 @@ import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.resources.DefaultResourcePack;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -17,7 +19,10 @@ import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -70,13 +75,28 @@ public class FontStrategist {
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
|
||||
// create and add the resource pack that provides fonts
|
||||
HashMap<String, File> packMap = new HashMap<>();
|
||||
final HashMap<String, File> packMap = new HashMap<>();
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
|
||||
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
|
||||
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
|
||||
}
|
||||
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
// Override vanilla resource pack with our textures
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap) {
|
||||
@Override
|
||||
public InputStream getInputStream(ResourceLocation location) throws IOException {
|
||||
final File file = packMap.get(location.toString());
|
||||
if (file != null && file.isFile()) {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
throw new FileNotFoundException(location.getResourcePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resourceExists(ResourceLocation location) {
|
||||
return packMap.containsKey(location.toString());
|
||||
}
|
||||
};
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
}
|
||||
@@ -119,6 +139,43 @@ public class FontStrategist {
|
||||
if (loaded > 0) LOGGER.info("Loaded {} bundled font(s) from the pack", loaded);
|
||||
}
|
||||
|
||||
private static volatile List<FontProviderBitmap> bitmapProviders;
|
||||
|
||||
private static List<FontProviderBitmap> bitmapProviders() {
|
||||
List<FontProviderBitmap> local = bitmapProviders;
|
||||
if (local == null) {
|
||||
synchronized (FontStrategist.class) {
|
||||
local = bitmapProviders;
|
||||
if (local == null) {
|
||||
local = FontProviderBitmap.loadProviders();
|
||||
bitmapProviders = local;
|
||||
}
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap findBitmap(int codepoint) {
|
||||
final List<FontProviderBitmap> providers = bitmapProviders();
|
||||
for (final FontProviderBitmap provider : providers) {
|
||||
if (provider.hasGlyph(codepoint)) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FontProviderBitmap findSupplementaryBitmap(int codepoint) {
|
||||
return EtFuturumFontCompat.MODERN_FONT_ENABLED ? findBitmap(codepoint) : null;
|
||||
}
|
||||
|
||||
public static void reloadFontResources() {
|
||||
synchronized (FontStrategist.class) {
|
||||
bitmapProviders = null;
|
||||
}
|
||||
FontProviderUnicode.get().reload();
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
|
||||
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
|
||||
@@ -136,8 +193,16 @@ public class FontStrategist {
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = FontProviderCustom.getFallback();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
if (!forceUnicode && EtFuturumFontCompat.MODERN_FONT_ENABLED) {
|
||||
final FontProviderBitmap bitmap = findBitmap(chr);
|
||||
if (bitmap != null) { return bitmap; }
|
||||
}
|
||||
return FontProviderUnicode.get();
|
||||
} else {
|
||||
if (!forceUnicode && !me.isSplash && EtFuturumFontCompat.MODERN_FONT_ENABLED) {
|
||||
final FontProviderBitmap bitmap = findBitmap(chr);
|
||||
if (bitmap != null) { return bitmap; }
|
||||
}
|
||||
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(false);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public interface SupplementaryGlyphSource extends FontProvider {
|
||||
|
||||
float getUStartCp(int cp);
|
||||
float getVStartCp(int cp);
|
||||
float getXAdvanceCp(int cp);
|
||||
float getGlyphWCp(int cp);
|
||||
float getUSizeCp(int cp);
|
||||
float getVSizeCp(int cp);
|
||||
ResourceLocation getTextureCp(int cp);
|
||||
|
||||
boolean isUnifont();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.compat.etfuturum;
|
||||
|
||||
import ganymedes01.etfuturum.configuration.configs.ConfigMixins;
|
||||
|
||||
public final class EtFuturumFontCompat {
|
||||
|
||||
public static final boolean MODERN_FONT_ENABLED = detect();
|
||||
|
||||
private EtFuturumFontCompat() {
|
||||
}
|
||||
|
||||
private static boolean detect() {
|
||||
try {
|
||||
return ConfigMixins.unicodeFontPages;
|
||||
} catch (LinkageError e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
-4
@@ -2,6 +2,12 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.fontrenderer;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.font.IFontParameters;
|
||||
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
|
||||
import com.gtnewhorizons.angelica.client.font.FontProviderUnicode;
|
||||
import com.gtnewhorizons.angelica.client.font.FontStrategist;
|
||||
import com.gtnewhorizons.angelica.compat.etfuturum.EtFuturumFontCompat;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_LENGTH;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
|
||||
@@ -127,6 +133,17 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onResourceManagerReload", at = @At("TAIL"))
|
||||
private void angelica$reloadModernFont(IResourceManager manager, CallbackInfo ci) {
|
||||
// FML's SplashFontRenderer calls onResourceManagerReload(null) on the splash thread
|
||||
if (manager == null) return;
|
||||
FontStrategist.reloadFontResources();
|
||||
try (InputStream in = manager.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream()) {
|
||||
FontProviderUnicode.readWidths(in, this.glyphWidth);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$charInRange(char what, char fromInclusive, char toInclusive) {
|
||||
return (what >= fromInclusive) && (what <= toInclusive);
|
||||
@@ -311,15 +328,115 @@ public abstract class MixinFontRenderer implements FontRendererAccessor, IFontPa
|
||||
}
|
||||
}
|
||||
|
||||
if (styles.length() == 0 && effects.length() == 0) return lastColor;
|
||||
if (styles.isEmpty() && effects.isEmpty()) return lastColor;
|
||||
final StringBuilder result = EXTRACT_RESULT; result.setLength(0);
|
||||
result.append(lastColor).append(styles).append(effects);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "getStringWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private String angelica$convertBeforeWidth(String str) {
|
||||
return ColorCodeUtils.convertAmpersandToSectionX(str);
|
||||
@Inject(method = "getStringWidth", at = @At("HEAD"), cancellable = true)
|
||||
private void angelica$batchedStringWidth(String text, CallbackInfoReturnable<Integer> cir) {
|
||||
cir.setReturnValue(angelica$getBatcher().getStringWidthBatched(text));
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$isFormatColor(char c) {
|
||||
return angelica$charInRange(c, '0', '9') || angelica$charInRange(c, 'a', 'f') || angelica$charInRange(c, 'A', 'F');
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean angelica$doesNotNeedsSurrogateWidth(String str) {
|
||||
if (str == null || !EtFuturumFontCompat.MODERN_FONT_ENABLED || this.unicodeFlag) return true;
|
||||
final int len = str.length();
|
||||
for (int i = 0; i + 1 < len; i++) {
|
||||
if (Character.isHighSurrogate(str.charAt(i)) && Character.isLowSurrogate(str.charAt(i + 1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Inject(method = "sizeStringToWidth", at = @At("HEAD"), cancellable = true)
|
||||
private void angelica$surrogateAwareSize(String str, int wrapWidth, CallbackInfoReturnable<Integer> cir) {
|
||||
if (angelica$doesNotNeedsSurrogateWidth(str)) return;
|
||||
final BatchingFontRenderer batcher = angelica$getBatcher();
|
||||
final int len = str.length();
|
||||
int width = 0;
|
||||
int lastSpace = -1;
|
||||
boolean bold = false;
|
||||
int idx = 0;
|
||||
for (; idx < len; idx++) {
|
||||
final char c0 = str.charAt(idx);
|
||||
if (c0 == '\n') {
|
||||
lastSpace = idx;
|
||||
break;
|
||||
} else if (c0 == FORMATTING_CHAR) {
|
||||
if (idx < len - 1) {
|
||||
final char code = str.charAt(idx + 1);
|
||||
idx++;
|
||||
if (code == 'l' || code == 'L') bold = true;
|
||||
else if (code == 'r' || code == 'R' || angelica$isFormatColor(code)) bold = false;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
if (c0 == ' ') lastSpace = idx;
|
||||
if (EtFuturumFontCompat.MODERN_FONT_ENABLED && !this.unicodeFlag && Character.isHighSurrogate(c0)
|
||||
&& idx + 1 < len && Character.isLowSurrogate(str.charAt(idx + 1))) {
|
||||
final int sup = batcher.supplementaryAdvance(Character.toCodePoint(c0, str.charAt(idx + 1)));
|
||||
if (sup >= 0) {
|
||||
width += sup;
|
||||
if (bold) width++;
|
||||
if (width > wrapWidth) break;
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
width += (int) batcher.getCharWidthFine(c0);
|
||||
if (bold) width++;
|
||||
}
|
||||
if (width > wrapWidth) break;
|
||||
}
|
||||
cir.setReturnValue(idx != len && lastSpace != -1 && lastSpace < idx ? lastSpace : idx);
|
||||
}
|
||||
|
||||
@Inject(method = "trimStringToWidth(Ljava/lang/String;IZ)Ljava/lang/String;", at = @At("HEAD"), cancellable = true)
|
||||
private void angelica$surrogateAwareTrim(String str, int width, boolean reverse, CallbackInfoReturnable<String> cir) {
|
||||
if (reverse || angelica$doesNotNeedsSurrogateWidth(str)) return;
|
||||
final BatchingFontRenderer batcher = angelica$getBatcher();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int len = str.length();
|
||||
int accum = 0;
|
||||
boolean prevFormat = false;
|
||||
boolean bold = false;
|
||||
for (int i = 0; i < len && accum < width; i++) {
|
||||
final char c0 = str.charAt(i);
|
||||
if (prevFormat) {
|
||||
// Vanilla trim only toggles bold on §l / §r here; color codes do not reset it
|
||||
prevFormat = false;
|
||||
if (c0 == 'l' || c0 == 'L') bold = true;
|
||||
else if (c0 == 'r' || c0 == 'R') bold = false;
|
||||
} else if (c0 == FORMATTING_CHAR) {
|
||||
prevFormat = true;
|
||||
} else {
|
||||
boolean pair = false;
|
||||
int adv;
|
||||
if (EtFuturumFontCompat.MODERN_FONT_ENABLED && !this.unicodeFlag && Character.isHighSurrogate(c0)
|
||||
&& i + 1 < len && Character.isLowSurrogate(str.charAt(i + 1))) {
|
||||
final int sup = batcher.supplementaryAdvance(Character.toCodePoint(c0, str.charAt(i + 1)));
|
||||
if (sup >= 0) { adv = sup; pair = true; } else adv = (int) batcher.getCharWidthFine(c0);
|
||||
} else {
|
||||
adv = (int) batcher.getCharWidthFine(c0);
|
||||
}
|
||||
accum += adv;
|
||||
if (bold) accum++;
|
||||
if (accum > width) break;
|
||||
sb.append(c0);
|
||||
if (pair) { sb.append(str.charAt(i + 1)); i++; }
|
||||
continue;
|
||||
}
|
||||
sb.append(c0);
|
||||
}
|
||||
cir.setReturnValue(sb.toString());
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "listFormattedStringToWidth", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
|
||||
Reference in new issue
Block a user