From cb06d149f7fbc51699b0f1722d3ba3849450a55e Mon Sep 17 00:00:00 2001 From: Antecer Date: Sat, 27 Apr 2024 12:49:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dttf=E5=AD=97=E4=BD=93?= =?UTF-8?q?=E8=BD=AE=E5=BB=93=E6=9F=A5=E8=AF=A2=E4=B8=8D=E5=88=B0=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/web/help/md/ruleHelp.md | 3 +- .../java/io/legado/app/help/JsExtensions.kt | 25 ++- .../app/model/analyzeRule/QueryTTF.java | 170 ++++++++---------- 3 files changed, 94 insertions(+), 104 deletions(-) diff --git a/app/src/main/assets/web/help/md/ruleHelp.md b/app/src/main/assets/web/help/md/ruleHelp.md index 394532778..0729f5453 100644 --- a/app/src/main/assets/web/help/md/ruleHelp.md +++ b/app/src/main/assets/web/help/md/ruleHelp.md @@ -178,7 +178,8 @@ let options = { if(b64){ var f1 = java.queryBase64TTF(b64[1]); var f2 = java.queryTTF("https://alanskycn.gitee.io/teachme/assets/font/Source Han Sans CN Regular.ttf"); - return java.replaceFont(result, f1, f2); + // return java.replaceFont(result, f1, f2); + return java.replaceFont(result, f1, f2, true); // 过滤掉f1中不存在的字形 } return result; })() diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index ca8e855e6..4973dab26 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -799,23 +799,34 @@ interface JsExtensions : JsEncodeUtils { fun replaceFont( text: String, errorQueryTTF: QueryTTF?, - correctQueryTTF: QueryTTF? + correctQueryTTF: QueryTTF?, + filter: Boolean ): String { if (errorQueryTTF == null || correctQueryTTF == null) return text val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节 contentArray.forEachIndexed { index, s -> val oldCode = s.codePointAt(0) - if (errorQueryTTF.inLimit(oldCode)) { - val glyf = errorQueryTTF.getGlyfByCode(oldCode) - val code = correctQueryTTF.getCodeByGlyf(glyf) - if (code != 0) { - contentArray[index] = code.toChar().toString() - } + val glyf = errorQueryTTF.getGlyfByCode(oldCode) + val code = correctQueryTTF.getCodeByGlyf(glyf) + if (code != 0) { + contentArray[index] = code.toChar().toString() + } + if (glyf == "" && filter) { + // 删除轮廓数据为空的字符 + contentArray[index] = "" } } return contentArray.joinToString("") } + fun replaceFont( + text: String, + errorQueryTTF: QueryTTF?, + correctQueryTTF: QueryTTF? + ): String { + return replaceFont(text, errorQueryTTF, correctQueryTTF, false) + } + /** * 章节数转数字 diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java index f140c8271..c87249cdc 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java +++ b/app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java @@ -11,7 +11,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -@SuppressWarnings({"FieldCanBeLocal", "StatementWithEmptyBody", "unused"}) +@SuppressWarnings({"FieldCanBeLocal", "unused"}) public class QueryTTF { private static class Header { public int majorVersion; @@ -222,7 +222,7 @@ public class QueryTTF { private final MaxpLayout maxp = new MaxpLayout(); private final List loca = new LinkedList<>(); private final CmapLayout Cmap = new CmapLayout(); - private final List glyf = new LinkedList<>(); + private final List glyf = new LinkedList<>(); @SuppressWarnings("unchecked") private final Pair[] pps = new Pair[]{ Pair.of(3, 10), @@ -233,10 +233,9 @@ public class QueryTTF { Pair.of(0, 1) }; - public final Map codeToGlyph = new HashMap<>(); - public final Map glyphToCode = new HashMap<>(); - private int limitMix = Integer.MAX_VALUE; - private int limitMax = 0; + public final Map unicodeToGlyph = new HashMap<>(); + public final Map glyphToUnicode = new HashMap<>(); + public final Map unicodeToGlyphIndex = new HashMap<>(); /** * 构造函数 @@ -406,83 +405,42 @@ public class QueryTTF { } } } - // 解析表 glyf (字体轮廓数据表) + // 读取表 glyf (字体轮廓数据表) for (Directory Temp : directorys) { if (Temp.tag.equals("glyf")) { - fontReader.index = Temp.offset; - for (int i = 0; i < maxp.numGlyphs; ++i) { + int glyfCount = maxp.numGlyphs; + for (int i = 0; i < glyfCount; ) { fontReader.index = Temp.offset + loca.get(i); + ++i; + int glyfNextIndex = i < glyfCount ? loca.get(i) : Temp.length; + byte[] glyph; short numberOfContours = fontReader.ReadInt16(); if (numberOfContours > 0) { - GlyfLayout g = new GlyfLayout(); - g.numberOfContours = numberOfContours; - g.xMin = fontReader.ReadInt16(); - g.yMin = fontReader.ReadInt16(); - g.xMax = fontReader.ReadInt16(); - g.yMax = fontReader.ReadInt16(); - g.endPtsOfContours = fontReader.GetUInt16Array(numberOfContours); - g.instructionLength = fontReader.ReadUInt16(); - g.instructions = fontReader.GetBytes(g.instructionLength); - int flagLength = g.endPtsOfContours[g.endPtsOfContours.length - 1] + 1; - // 获取轮廓点描述标志 - g.flags = new byte[flagLength]; - for (int n = 0; n < flagLength; ++n) { - g.flags[n] = fontReader.GetByte(); - if ((g.flags[n] & 0x08) != 0x00) { - for (int m = fontReader.ReadUInt8(); m > 0; --m) { - g.flags[++n] = g.flags[n - 1]; - } - } - } - // 获取轮廓点描述x轴相对值 - g.xCoordinates = new short[flagLength]; - for (int n = 0; n < flagLength; ++n) { - short same = (short) ((g.flags[n] & 0x10) != 0 ? 1 : -1); - if ((g.flags[n] & 0x02) != 0) { - g.xCoordinates[n] = (short) (same * fontReader.ReadUInt8()); - } else { - g.xCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16(); - } - } - // 获取轮廓点描述y轴相对值 - g.yCoordinates = new short[flagLength]; - for (int n = 0; n < flagLength; ++n) { - short same = (short) ((g.flags[n] & 0x20) != 0 ? 1 : -1); - if ((g.flags[n] & 0x04) != 0) { - g.yCoordinates[n] = (short) (same * fontReader.ReadUInt8()); - } else { - g.yCoordinates[n] = same == 1 ? (short) 0 : fontReader.ReadInt16(); - } - } - /* 相对坐标转绝对坐标 - for (int n = 1; n < flagLength; ++n) { - xCoordinates[n] += xCoordinates[n - 1]; - yCoordinates[n] += yCoordinates[n - 1]; - }*/ - glyf.add(g); + short g_xMin = fontReader.ReadInt16(); + short g_yMin = fontReader.ReadInt16(); + short g_xMax = fontReader.ReadInt16(); + short g_yMax = fontReader.ReadInt16(); + int[] endPtsOfContours = fontReader.GetUInt16Array(numberOfContours); + glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - Temp.offset)); } else { - // 复合字体暂未使用 + glyph = fontReader.GetBytes(glyfNextIndex - (fontReader.index - 2)); } + glyf.add(getHexFromBytes(glyph)); } } } // 建立Unicode&Glyph双向表 for (int key = 0; key < 130000; ++key) { - if (key == 0xFF) key = 0x3400; - int gid = getGlyfIndex(key); - if (gid == 0 || gid >= glyf.size()) continue; - StringBuilder sb = new StringBuilder(); - // 字型数据转String,方便存HashMap - for (short b : glyf.get(gid).xCoordinates) sb.append(b); - for (short b : glyf.get(gid).yCoordinates) sb.append(b); - String val = sb.toString(); - if (limitMix > key) limitMix = key; - if (limitMax < key) limitMax = key; - codeToGlyph.put(key, val); - if (glyphToCode.containsKey(val)) continue; - glyphToCode.put(val, key); +// if (key == 0xFF) key = 0x3400; + int gid = queryGlyfIndex(key); + if (gid >= glyf.size()) continue; + unicodeToGlyphIndex.put(key, gid); + var val = glyf.get(gid); + unicodeToGlyph.put(key, val); + if (glyphToUnicode.containsKey(val)) continue; + glyphToUnicode.put(val, key); } } @@ -509,11 +467,11 @@ public class QueryTTF { /** * 使用Unicode值查找轮廓索引 * - * @param code 传入Unicode十进制值 + * @param unicode 传入Unicode值 * @return 返回十进制轮廓索引 */ - private int getGlyfIndex(int code) { - if (code == 0) return 0; + public int queryGlyfIndex(int unicode) { + if (unicode == 0) return 0; int fmtKey = 0; for (Pair item : pps) { for (CmapRecord record : Cmap.records) { @@ -531,34 +489,33 @@ public class QueryTTF { assert table != null; int fmt = table.format; if (fmt == 0) { - if (code < table.glyphIdArray.length) glyfID = table.glyphIdArray[code] & 0xFF; + if (unicode < table.glyphIdArray.length) glyfID = table.glyphIdArray[unicode] & 0xFF; } else if (fmt == 4) { CmapFormat4 tab = (CmapFormat4) table; - if (code > tab.endCode[tab.endCode.length - 1]) return 0; + if (unicode > tab.endCode[tab.endCode.length - 1]) return 0; // 二分法查找数值索引 int start = 0, middle, end = tab.endCode.length - 1; while (start + 1 < end) { middle = (start + end) / 2; - if (tab.endCode[middle] <= code) start = middle; + if (tab.endCode[middle] <= unicode) start = middle; else end = middle; } - if (tab.endCode[start] < code) ++start; - if (code < tab.startCode[start]) return 0; + if (tab.endCode[start] < unicode) ++start; + if (unicode < tab.startCode[start]) return 0; if (tab.idRangeOffset[start] != 0) { - glyfID = tab.glyphIdArray[code - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)]; - } else glyfID = code + tab.idDelta[start]; + glyfID = tab.glyphIdArray[unicode - tab.startCode[start] + (tab.idRangeOffset[start] >> 1) - (tab.idRangeOffset.length - start)]; + } else glyfID = unicode + tab.idDelta[start]; glyfID &= 0xFFFF; } else if (fmt == 6) { CmapFormat6 tab = (CmapFormat6) table; - int index = code - tab.firstCode; - if (index < 0 || index >= tab.glyphIdArray.length) glyfID = 0; - else glyfID = tab.glyphIdArray[index]; + int index = unicode - tab.firstCode; + if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index]; } else if (fmt == 12) { CmapFormat12 tab = (CmapFormat12) table; - if (code > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0; + if (unicode > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0; for (int i = 0; i < tab.numGroups; i++) { - if (tab.groups.get(i).getLeft() <= code && code <= tab.groups.get(i).getMiddle()) { - glyfID = tab.groups.get(i).getRight() + code - tab.groups.get(i).getLeft(); + if (tab.groups.get(i).getLeft() <= unicode && unicode <= tab.groups.get(i).getMiddle()) { + glyfID = tab.groups.get(i).getRight() + unicode - tab.groups.get(i).getLeft(); break; } } @@ -567,33 +524,54 @@ public class QueryTTF { } /** - * 判断Unicode值是否在字体范围内 + * 使用Unicode值获取轮廓索引 * - * @param code 传入Unicode十进制值 - * @return 返回bool查询结果 + * @param unicode 传入Unicode值 + * @return 返回十进制轮廓索引 */ - public boolean inLimit(int code) { - return (limitMix <= code) && (code <= limitMax); + public int getGlyfIndex(int unicode) { + var glyfIndex = unicodeToGlyphIndex.get(unicode); + if (glyfIndex == null) return 0; + return glyfIndex; } /** * 使用Unicode值获取轮廓数据 * - * @param key 传入Unicode十进制值 + * @param unicode 传入Unicode值 * @return 返回轮廓数组的String值 */ - public String getGlyfByCode(int key) { - return codeToGlyph.getOrDefault(key, ""); + public String getGlyfByCode(int unicode) { + return unicodeToGlyph.getOrDefault(unicode, ""); } /** * 使用轮廓数据获取Unicode值 * - * @param val 传入轮廓数组的String值 + * @param glyph 传入轮廓数组的String值 * @return 返回Unicode十进制值 */ - public int getCodeByGlyf(String val) { + public int getCodeByGlyf(String glyph) { //noinspection ConstantConditions - return glyphToCode.getOrDefault(val, 0); + return glyphToUnicode.getOrDefault(glyph, 0); + } + + /** + * 字体轮廓数据转Hex字符串 + * + * @param glyph 字体轮廓数据 + * @return 返回轮廓数组的String值 + */ + public String getHexFromBytes(byte[] glyph) { + if (glyph == null) return ""; + StringBuilder sb = new StringBuilder(); + for (byte b : glyph) { + String hex = Integer.toHexString(b); + if (hex.length() == 1) { + sb.append("0");//当16进制为个位数时,在前面补0 + } + sb.append(hex);//将16进制加入字符串 + } + return sb.toString().toUpperCase(); } }