实现网页内嵌字体替换功能

pull/493/head
Antecer 4 years ago
parent 2385c3ebc8
commit 6a91fd6630
  1. 605
      app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java
  2. 602
      app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.kt
  3. 6
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt

@ -0,0 +1,605 @@
package io.legado.app.model.analyzeRule;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class QueryTTF {
private class Header {
public int majorVersion;
public int minorVersion;
public int numOfTables;
public int searchRange;
public int entrySelector;
public int rangeShift;
}
private class Directory {
public String tag; // table name
public int checkSum; // Check sum
public int offset; // Offset from beginning of file
public int length; // length of the table in bytes
}
private class NameLayout {
public int format;
public int count;
public int stringOffset;
public List<NameRecord> records = new LinkedList<>();
}
private class NameRecord {
public int platformID; // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom>
public int encodingID; // 编码标识符
public int languageID; // 语言标识符
public int nameID; // 名称标识符
public int length; // 名称字符串的长度
public int offset; // 名称字符串相对于stringOffset的字节偏移量
}
private class HeadLayout {
public int majorVersion;
public int minorVersion;
public int fontRevision;
public int checkSumAdjustment;
public int magicNumber;
public int flags;
public int unitsPerEm;
public long created;
public long modified;
public short xMin;
public short yMin;
public short xMax;
public short yMax;
public int macStyle;
public int lowestRecPPEM;
public short fontDirectionHint;
public short indexToLocFormat; // <0:loca是2字节数组, 1:loca是4字节数组>
public short glyphDataFormat;
}
private class MaxpLayout {
public int majorVersion;
public int minorVersion;
public int numGlyphs; // 字体中的字形数量
public int maxPoints;
public int maxContours;
public int maxCompositePoints;
public int maxCompositeContours;
public int maxZones;
public int maxTwilightPoints;
public int maxStorage;
public int maxFunctionDefs;
public int maxInstructionDefs;
public int maxStackElements;
public int maxSizeOfInstructions;
public int maxComponentElements;
public int maxComponentDepth;
}
private class CmapLayout {
public int version;
public int numTables;
public List<CmapRecord> records = new LinkedList<>();
public Map<Integer, CmapFormat> tables = new HashMap<>();
}
private class CmapRecord {
public int platformID;
public int encodingID;
public int offset;
}
private class CmapFormat {
public int format;
public int length;
public int language;
public byte[] glyphIdArray;
}
private class CmapFormat4 extends CmapFormat {
public int segCountX2;
public int searchRange;
public int entrySelector;
public int rangeShift;
public int[] endCode;
public int reservedPad;
public int[] startCode;
public short[] idDelta;
public int[] idRangeOffset;
public int[] glyphIdArray;
}
private class CmapFormat6 extends CmapFormat {
public int firstCode;
public int entryCount;
public int[] glyphIdArray;
}
private class CmapFormat12 extends CmapFormat {
public int reserved;
public int length;
public int language;
public int numGroups;
public List<Triple<Integer, Integer, Integer>> groups;
}
private class GlyfLayout {
public short numberOfContours; // 非负值为简单字型,负值为符合字型
public short xMin;
public short yMin;
public short xMax;
public short yMax;
public int[] endPtsOfContours; // length=numberOfContours
public int instructionLength;
public byte[] instructions; // length=instructionLength
public byte[] flags;
public short[] xCoordinates; // length = flags.length
public short[] yCoordinates; // length = flags.length
}
private class ByteArrayReader {
public int Index;
public byte[] Buffer;
public ByteArrayReader(byte[] buffer, int index) {
Buffer = buffer;
Index = index;
}
public long ReadUIntX(long len) {
long result = 0;
for (long i = 0; i < len; ++i) {
result <<= 8;
result |= Buffer[Index++] & 0xFF;
}
return result;
}
public long ReadUInt64() {
return ReadUIntX(8);
}
public long ReadInt64() {
return ReadUIntX(8);
}
public int ReadUInt32() {
return (int) ReadUIntX(4);
}
public int ReadInt32() {
return (int) ReadUIntX(4);
}
public int ReadUInt16() {
return (int) ReadUIntX(2);
}
public short ReadInt16() {
return (short) ReadUIntX(2);
}
public short ReadUInt8() {
return (short) ReadUIntX(1);
}
public String ReadStrings(int len, Charset charset) {
byte[] result = len > 0 ? new byte[len] : null;
for (int i = 0; i < len; ++i) result[i] = Buffer[Index++];
return new String(result, charset);
}
public byte GetByte() {
return Buffer[Index++];
}
public byte[] GetBytes(int len) {
byte[] result = len > 0 ? new byte[len] : null;
for (int i = 0; i < len; ++i) result[i] = Buffer[Index++];
return result;
}
public int[] GetUInt16Array(int len) {
int[] result = len > 0 ? new int[len] : null;
for (int i = 0; i < len; ++i) result[i] = ReadUInt16();
return result;
}
public short[] GetInt16Array(int len) {
short[] result = len > 0 ? new short[len] : null;
for (int i = 0; i < len; ++i) result[i] = ReadInt16();
return result;
}
}
private final ByteArrayReader FontReader;
private final Header FileHeader = new Header();
private final List<Directory> Directorys = new LinkedList<>();
private final NameLayout Name = new NameLayout();
private final HeadLayout Head = new HeadLayout();
private final MaxpLayout Maxp = new MaxpLayout();
private final List<Integer> Loca = new LinkedList<>();
private final CmapLayout Cmap = new CmapLayout();
private final List<GlyfLayout> Glyf = new LinkedList<>();
private final Map<Integer, short[]> UnicodeMap = new HashMap<>();
private final Pair<Integer, Integer>[] pps = new Pair[]{
Pair.of(3, 10),
Pair.of(0, 4),
Pair.of(3, 1),
Pair.of(1, 0),
Pair.of(0, 3),
Pair.of(0, 1)
};
/**
* 构造函数
*
* @param buffer 传入TTF字体二进制数组
*/
public QueryTTF(byte[] buffer) {
FontReader = new ByteArrayReader(buffer, 0);
// 获取文件头
FileHeader.majorVersion = FontReader.ReadUInt16();
FileHeader.minorVersion = FontReader.ReadUInt16();
FileHeader.numOfTables = FontReader.ReadUInt16();
FileHeader.searchRange = FontReader.ReadUInt16();
FileHeader.entrySelector = FontReader.ReadUInt16();
FileHeader.rangeShift = FontReader.ReadUInt16();
// 获取目录
for (int i = 0; i < FileHeader.numOfTables; ++i) {
Directory d = new Directory();
d.tag = FontReader.ReadStrings(4, StandardCharsets.US_ASCII);
d.checkSum = FontReader.ReadUInt32();
d.offset = FontReader.ReadUInt32();
d.length = FontReader.ReadUInt32();
Directorys.add(d);
}
// 解析表 name (字体信息,包含版权、名称、作者等...)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("name")) {
FontReader.Index = Temp.offset;
Name.format = FontReader.ReadUInt16();
Name.count = FontReader.ReadUInt16();
Name.stringOffset = FontReader.ReadUInt16();
for (int i = 0; i < Name.count; ++i) {
NameRecord record = new NameRecord();
record.platformID = FontReader.ReadUInt16();
record.encodingID = FontReader.ReadUInt16();
record.languageID = FontReader.ReadUInt16();
record.nameID = FontReader.ReadUInt16();
record.length = FontReader.ReadUInt16();
record.offset = FontReader.ReadUInt16();
Name.records.add(record);
}
}
}
// 解析表 head (获取 head.indexToLocFormat)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("head")) {
FontReader.Index = Temp.offset;
Head.majorVersion = FontReader.ReadUInt16();
Head.minorVersion = FontReader.ReadUInt16();
Head.fontRevision = FontReader.ReadUInt32();
Head.checkSumAdjustment = FontReader.ReadUInt32();
Head.magicNumber = FontReader.ReadUInt32();
Head.flags = FontReader.ReadUInt16();
Head.unitsPerEm = FontReader.ReadUInt16();
Head.created = FontReader.ReadUInt64();
Head.modified = FontReader.ReadUInt64();
Head.xMin = FontReader.ReadInt16();
Head.yMin = FontReader.ReadInt16();
Head.xMax = FontReader.ReadInt16();
Head.yMax = FontReader.ReadInt16();
Head.macStyle = FontReader.ReadUInt16();
Head.lowestRecPPEM = FontReader.ReadUInt16();
Head.fontDirectionHint = FontReader.ReadInt16();
Head.indexToLocFormat = FontReader.ReadInt16();
Head.glyphDataFormat = FontReader.ReadInt16();
}
}
// 解析表 maxp (获取 maxp.numGlyphs)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("maxp")) {
FontReader.Index = Temp.offset;
Maxp.majorVersion = FontReader.ReadUInt16();
Maxp.minorVersion = FontReader.ReadUInt16();
Maxp.numGlyphs = FontReader.ReadUInt16();
Maxp.maxPoints = FontReader.ReadUInt16();
Maxp.maxContours = FontReader.ReadUInt16();
Maxp.maxCompositePoints = FontReader.ReadUInt16();
Maxp.maxCompositeContours = FontReader.ReadUInt16();
Maxp.maxZones = FontReader.ReadUInt16();
Maxp.maxTwilightPoints = FontReader.ReadUInt16();
Maxp.maxStorage = FontReader.ReadUInt16();
Maxp.maxFunctionDefs = FontReader.ReadUInt16();
Maxp.maxInstructionDefs = FontReader.ReadUInt16();
Maxp.maxStackElements = FontReader.ReadUInt16();
Maxp.maxSizeOfInstructions = FontReader.ReadUInt16();
Maxp.maxComponentElements = FontReader.ReadUInt16();
Maxp.maxComponentDepth = FontReader.ReadUInt16();
}
}
// 解析表 loca (轮廓数据偏移地址表)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("loca")) {
FontReader.Index = Temp.offset;
int offset = Head.indexToLocFormat == 0 ? 2 : 4;
for (long i = 0; i < Temp.length; i += offset) {
Loca.add(offset == 2 ? FontReader.ReadUInt16() << 1 : FontReader.ReadUInt32());
}
}
}
// 解析表 cmap (Unicode编码轮廓索引对照表)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("cmap")) {
FontReader.Index = Temp.offset;
Cmap.version = FontReader.ReadUInt16();
Cmap.numTables = FontReader.ReadUInt16();
for (int i = 0; i < Cmap.numTables; ++i) {
CmapRecord record = new CmapRecord();
record.platformID = FontReader.ReadUInt16();
record.encodingID = FontReader.ReadUInt16();
record.offset = FontReader.ReadUInt32();
Cmap.records.add(record);
}
for (int i = 0; i < Cmap.numTables; ++i) {
int fmtOffset = Cmap.records.get(i).offset;
FontReader.Index = Temp.offset + fmtOffset;
int EndIndex = FontReader.Index;
int format = FontReader.ReadUInt16();
if (Cmap.tables.containsKey(fmtOffset)) continue;
if (format == 0) {
CmapFormat f = new CmapFormat();
f.format = format;
f.length = FontReader.ReadUInt16();
f.language = FontReader.ReadUInt16();
f.glyphIdArray = FontReader.GetBytes(f.length - 6);
Cmap.tables.put(fmtOffset, f);
} else if (format == 4) {
CmapFormat4 f = new CmapFormat4();
f.format = format;
f.length = FontReader.ReadUInt16();
f.language = FontReader.ReadUInt16();
f.segCountX2 = FontReader.ReadUInt16();
int segCount = f.segCountX2 >> 1;
f.searchRange = FontReader.ReadUInt16();
f.entrySelector = FontReader.ReadUInt16();
f.rangeShift = FontReader.ReadUInt16();
f.endCode = FontReader.GetUInt16Array(segCount);
f.reservedPad = FontReader.ReadUInt16();
f.startCode = FontReader.GetUInt16Array(segCount);
f.idDelta = FontReader.GetInt16Array(segCount);
f.idRangeOffset = FontReader.GetUInt16Array(segCount);
f.glyphIdArray = FontReader.GetUInt16Array((EndIndex + f.length - FontReader.Index) >> 1);
Cmap.tables.put(fmtOffset, f);
} else if (format == 6) {
CmapFormat6 f = new CmapFormat6();
f.format = format;
f.length = FontReader.ReadUInt16();
f.language = FontReader.ReadUInt16();
f.firstCode = FontReader.ReadUInt16();
f.entryCount = FontReader.ReadUInt16();
f.glyphIdArray = FontReader.GetUInt16Array(f.entryCount);
Cmap.tables.put(fmtOffset, f);
} else if (format == 12) {
CmapFormat12 f = new CmapFormat12();
f.format = format;
f.reserved = FontReader.ReadUInt16();
f.length = FontReader.ReadUInt32();
f.language = FontReader.ReadUInt32();
f.numGroups = FontReader.ReadUInt32();
f.groups = new ArrayList<>(f.numGroups);
for (int n = 0; n < f.numGroups; ++n) {
f.groups.add(Triple.of(FontReader.ReadUInt32(), FontReader.ReadUInt32(), FontReader.ReadUInt32()));
}
Cmap.tables.put(fmtOffset, f);
}
}
}
}
// 解析表 glyf (字体轮廓数据表)
for (Directory Temp : Directorys) {
if (Temp.tag.equals("glyf")) {
FontReader.Index = Temp.offset;
for (int i = 0; i < Maxp.numGlyphs; ++i) {
FontReader.Index = Temp.offset + Loca.get(i);
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);
} else {
// 复合字体暂未使用
}
}
}
}
// 建立Unicode&Glyf映射表
for (int i = 0; i < 130000; ++i) {
int gid = GetGlyfIndex(i);
if (gid == 0) continue;
int thisLength = Glyf.get(gid).flags.length;
short[] thisGlyf = new short[thisLength << 1];
System.arraycopy(Glyf.get(gid).xCoordinates, 0, thisGlyf, 0, thisLength);
System.arraycopy(Glyf.get(gid).yCoordinates, 0, thisGlyf, thisLength, thisLength);
UnicodeMap.put(i, thisGlyf);
}
}
/**
* 获取字体信息 (1=字体名称)
*
* @param nameId 传入十进制字体信息索引
* @return 返回查询结果字符串
*/
public String GetNameById(int nameId) {
for (Directory Temp : Directorys) {
if (!Temp.tag.equals("name")) continue;
FontReader.Index = Temp.offset;
break;
}
for (NameRecord record : Name.records) {
if (record.nameID != nameId) continue;
FontReader.Index += Name.stringOffset + record.offset;
return FontReader.ReadStrings(record.length, record.platformID == 1 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16BE);
}
return "error";
}
/**
* 使用Unicode值查找轮廓索引
*
* @param code 传入Unicode十进制值
* @return 返回十进制轮廓索引
*/
private int GetGlyfIndex(int code) {
if (code == 0) return 0;
int fmtKey = 0;
for (Pair<Integer, Integer> item : pps) {
for (CmapRecord record : Cmap.records) {
if ((item.getLeft() == record.platformID) && (item.getRight() == record.encodingID)) {
fmtKey = record.offset;
break;
}
}
if (fmtKey > 0) break;
}
if (fmtKey == 0) return 0;
int glyfID = 0;
CmapFormat table = Cmap.tables.get(fmtKey);
int fmt = table.format;
if (fmt == 0) {
if (code >= table.glyphIdArray.length) glyfID = 0;
else glyfID = table.glyphIdArray[code] & 0xFF;
} else if (fmt == 4) {
CmapFormat4 tab = (CmapFormat4) table;
if (code > 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;
else end = middle;
}
if (tab.endCode[start] < code) ++start;
if (code < 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 &= 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];
} else if (fmt == 12) {
CmapFormat12 tab = (CmapFormat12) table;
if (code > tab.groups.get(tab.numGroups - 1).getMiddle()) return 0;
// 二分法查找数值索引
int start = 0, middle, end = tab.numGroups - 1;
while (start + 1 < end) {
middle = (start + end) / 2;
if (tab.groups.get(middle).getLeft() <= code) start = middle;
else end = middle;
}
if (tab.groups.get(start).getLeft() <= code && code <= tab.groups.get(start).getMiddle()) {
glyfID = tab.groups.get(start).getRight() + code - tab.groups.get(start).getLeft();
}
}
return glyfID;
}
/**
* 使用轮廓数据获取Unicode值
*
* @param inputGlyf 传入short[]轮廓数组
* @return 返回Unicode十进制值
*/
public int GetCodeByGlyf(short[] inputGlyf) {
int unicodeVal = 0;
for (Map.Entry<Integer, short[]> g : UnicodeMap.entrySet()) {
if (inputGlyf.length != g.getValue().length) continue;
int i = inputGlyf.length;
while (i > 0) {
--i;
if (inputGlyf[i] != g.getValue()[i]) {
++i;
break;
}
}
if (i == 0) unicodeVal = g.getKey();
}
return unicodeVal;
}
/**
* 使用Unicode值获取轮廓数据
*
* @param code 传入Unicode十进制值
* @return 返回short[]轮廓数组
*/
public short[] GetGlyfByCode(int code) {
if (code <= 0) return new short[0];
return UnicodeMap.getOrDefault(code, new short[0]);
}
}

@ -1,602 +0,0 @@
package io.legado.app.model.analyzeRule
import io.legado.app.help.JsExtensions
import java.nio.charset.Charset
import kotlin.experimental.and
/**
* 解析TTF字体
* @see <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/">获取详情</a>
* @see <a href="https://photopea.github.io/Typr.js/demo/index.html">基于Javascript的TTF解析器</a>
*/
@ExperimentalUnsignedTypes
class QueryTTF(var Font: ByteArray) : JsExtensions {
private class Header {
var majorVersion: UShort = 0u
var minorVersion: UShort = 0u
var numOfTables: UShort = 0u
var searchRange: UShort = 0u
var entrySelector: UShort = 0u
var rangeShift: UShort = 0u
}
private class Directory {
var tag: String = ""
var checkSum: UInt = 0u
var offset: UInt = 0u
var length: UInt = 0u
}
private class NameLayout {
var format: UShort = 0u
var count: UShort = 0u
var stringOffset: UShort = 0u
var records = ArrayList<NameRecord>()
}
private class NameRecord {
var platformID: UShort = 0u
var encodingID: UShort = 0u
var languageID: UShort = 0u
var nameID: UShort = 0u
var length: UShort = 0u
var offset: UShort = 0u
}
private class HeadLayout {
var majorVersion: UShort = 0u
var minorVersion: UShort = 0u
var fontRevision: UInt = 0u
var checkSumAdjustment: UInt = 0u
var magicNumber: UInt = 0u
var flags: UShort = 0u
var unitsPerEm: UShort = 0u
var created: ULong = 0u
var modified: ULong = 0u
var xMin: Short = 0
var yMin: Short = 0
var xMax: Short = 0
var yMax: Short = 0
var macStyle: UShort = 0u
var lowestRecPPEM: UShort = 0u
var fontDirectionHint: Short = 0
var indexToLocFormat: Short = 0
var glyphDataFormat: Short = 0
}
private class MaxpLayout {
var majorVersion: UShort = 0u
var minorVersion: UShort = 0u
var numGlyphs: UShort = 0u // 字体中的字形数量
var maxPoints: UShort = 0u
var maxContours: UShort = 0u
var maxCompositePoints: UShort = 0u
var maxCompositeContours: UShort = 0u
var maxZones: UShort = 0u
var maxTwilightPoints: UShort = 0u
var maxStorage: UShort = 0u
var maxFunctionDefs: UShort = 0u
var maxInstructionDefs: UShort = 0u
var maxStackElements: UShort = 0u
var maxSizeOfInstructions: UShort = 0u
var maxComponentElements: UShort = 0u
var maxComponentDepth: UShort = 0u
}
private class CmapLayout {
var version: UShort = 0u
var numTables: UShort = 0u
var records = ArrayList<CmapRecord>()
var tables = mutableMapOf<UInt, Any>()
}
private class CmapRecord {
var platformID: UShort = 0u
var encodingID: UShort = 0u
var offset: UInt = 0u
}
private class CmapFormat0 {
var format: UShort = 0u
var length: UShort = 0u
var language: UShort = 0u
var glyphIdArray = ArrayList<Byte>()
}
private class CmapFormat4 {
var format: UShort = 0u
var length: UShort = 0u
var language: UShort = 0u
var segCountX2: UShort = 0u
var searchRange: UShort = 0u
var entrySelector: UShort = 0u
var rangeShift: UShort = 0u
lateinit var endCode: ArrayList<UShort> // UInt16[]
var reservedPad: UShort = 0u
lateinit var startCode: ArrayList<UShort> // UInt16[]
lateinit var idDelta: ArrayList<Short>
lateinit var idRangeOffset: ArrayList<UShort> // UInt16[]
var glyphIdArray = ArrayList<UShort>()
}
private class CmapFormat6 {
var format: UShort = 0u
var length: UShort = 0u
var language: UShort = 0u
var firstCode: UShort = 0u
var entryCount: UShort = 0u
var glyphIdArray = ArrayList<UShort>()
}
private class CmapFormat12 {
var format: UShort = 0u
var reserved: UShort = 0u
var length: UInt = 0u
var language: UInt = 0u
var numGroups: UInt = 0u
lateinit var groups: ArrayList<Triple<UInt, UInt, UInt>>
}
private class GlyfLayout {
var numberOfContours: Short = 0
var xMin: Short = 0
var yMin: Short = 0
var xMax: Short = 0
var yMax: Short = 0
lateinit var endPtsOfContours: ArrayList<UShort> // UInt16[]
var instructionLength: UShort = 0u // UInt16
lateinit var instructions: ArrayList<Byte>
lateinit var flags: ArrayList<Byte>
lateinit var xCoordinates: ArrayList<Short>
lateinit var yCoordinates: ArrayList<Short>
}
private class ByteArrayReader(var Buffer: ByteArray, var Index: Int) {
fun ReadUIntX(len: Long): ULong {
var result: ULong = 0u
for (i in 0 until len) {
result.shl(8)
result = result or Buffer[Index++].toULong()
}
return result
}
fun ReadUInt64(): ULong {
return ReadUIntX(8)
}
fun ReadInt64(): Long {
return ReadUIntX(8).toLong()
}
fun ReadUInt32(): UInt {
return ReadUIntX(4).toUInt()
}
fun ReadInt32(): Int {
return ReadUIntX(4).toInt()
}
fun ReadUInt16(): UShort {
return ReadUIntX(2).toUShort()
}
fun ReadInt16(): Short {
return ReadUIntX(2).toShort()
}
fun ReadStrings(len: Int, charset: Charset): String {
if (len <= 0) return ""
val result = ByteArray(len)
for (i in 0 until len) {
result[i] = Buffer[Index++]
}
return result.toString(charset)
}
fun GetByte(): Byte {
return Buffer[Index++]
}
fun GetBytes(len: Int): ArrayList<Byte> {
if (len <= 0) return ArrayList(0)
val result = ArrayList<Byte>(len)
for (i in 0 until len) {
result[i] = Buffer[Index++]
}
return result
}
fun GetUInt16Array(len: Int): ArrayList<UShort> {
if (len <= 0) return ArrayList(0)
val result = ArrayList<UShort>(len)
for (i in 0 until len) {
result[i] = ReadUInt16()
}
return result
}
fun GetInt16Array(len: Int): ArrayList<Short> {
if (len <= 0) return ArrayList(0)
val result = ArrayList<Short>(len)
for (i in 0 until len) {
result[i] = ReadInt16()
}
return result
}
}
private var FontReader: ByteArrayReader
private var fileHeader = Header()
private var directorys = ArrayList<Directory>()
private var name = NameLayout()
private var head = HeadLayout()
private var maxp = MaxpLayout()
private var loca = ArrayList<UInt>()
private var cmap = CmapLayout()
private var glyf = ArrayList<GlyfLayout>()
private var unicodeMap = mutableMapOf<Int, ArrayList<Short>>()
init {
FontReader = ByteArrayReader(Font, 0)
// 获取文件头
fileHeader.majorVersion = FontReader.ReadUInt16()
fileHeader.minorVersion = FontReader.ReadUInt16()
fileHeader.numOfTables = FontReader.ReadUInt16()
fileHeader.searchRange = FontReader.ReadUInt16()
fileHeader.entrySelector = FontReader.ReadUInt16()
fileHeader.rangeShift = FontReader.ReadUInt16()
// 获取目录
for (i in 0 until fileHeader.numOfTables.toInt()) {
val tag = FontReader.ReadStrings(4, Charsets.US_ASCII)
val t = Directory()
t.tag = tag
t.checkSum = FontReader.ReadUInt32()
t.offset = FontReader.ReadUInt32()
t.length = FontReader.ReadUInt32()
directorys.add(t)
}
// 解析表 name (字体信息,包含版权、名称、作者等...)
for (Temp in directorys) {
if (Temp.tag == "name") {
FontReader.Index = Temp.offset.toInt()
name.format = FontReader.ReadUInt16()
name.count = FontReader.ReadUInt16()
name.stringOffset = FontReader.ReadUInt16()
for (i in 0 until name.count.toInt()) {
val record = NameRecord()
record.platformID = FontReader.ReadUInt16()
record.encodingID = FontReader.ReadUInt16()
record.languageID = FontReader.ReadUInt16()
record.nameID = FontReader.ReadUInt16()
record.length = FontReader.ReadUInt16()
record.offset = FontReader.ReadUInt16()
name.records.add(record)
}
}
}
// 解析表 head (获取 head.indexToLocFormat)
for (Temp in directorys) {
if (Temp.tag == "head") {
FontReader.Index = Temp.offset.toInt()
head.majorVersion = FontReader.ReadUInt16()
head.minorVersion = FontReader.ReadUInt16()
head.fontRevision = FontReader.ReadUInt32()
head.checkSumAdjustment = FontReader.ReadUInt32()
head.magicNumber = FontReader.ReadUInt32()
head.flags = FontReader.ReadUInt16()
head.unitsPerEm = FontReader.ReadUInt16()
head.created = FontReader.ReadUInt64()
head.modified = FontReader.ReadUInt64()
head.xMin = FontReader.ReadInt16()
head.yMin = FontReader.ReadInt16()
head.xMax = FontReader.ReadInt16()
head.yMax = FontReader.ReadInt16()
head.macStyle = FontReader.ReadUInt16()
head.lowestRecPPEM = FontReader.ReadUInt16()
head.fontDirectionHint = FontReader.ReadInt16()
head.indexToLocFormat = FontReader.ReadInt16()
head.glyphDataFormat = FontReader.ReadInt16()
}
}
// 解析表 maxp (获取 maxp.numGlyphs)
for (Temp in directorys) {
if (Temp.tag == "maxp") {
FontReader.Index = Temp.offset.toInt()
maxp.majorVersion = FontReader.ReadUInt16()
maxp.minorVersion = FontReader.ReadUInt16()
maxp.numGlyphs = FontReader.ReadUInt16()
maxp.maxPoints = FontReader.ReadUInt16()
maxp.maxContours = FontReader.ReadUInt16()
maxp.maxCompositePoints = FontReader.ReadUInt16()
maxp.maxCompositeContours = FontReader.ReadUInt16()
maxp.maxZones = FontReader.ReadUInt16()
maxp.maxTwilightPoints = FontReader.ReadUInt16()
maxp.maxStorage = FontReader.ReadUInt16()
maxp.maxFunctionDefs = FontReader.ReadUInt16()
maxp.maxInstructionDefs = FontReader.ReadUInt16()
maxp.maxStackElements = FontReader.ReadUInt16()
maxp.maxSizeOfInstructions = FontReader.ReadUInt16()
maxp.maxComponentElements = FontReader.ReadUInt16()
maxp.maxComponentDepth = FontReader.ReadUInt16()
}
}
// 解析表 loca (轮廓数据偏移地址表)
for (Temp in directorys) {
if (Temp.tag == "loca") {
FontReader.Index = Temp.offset.toInt()
val offset: UInt = if (head.indexToLocFormat.toInt() == 0) 2u else 4u
var i: UInt = 0u
while (i < Temp.length) {
loca.add(if (offset == 2u) FontReader.ReadUInt16().toUInt().shl(1) else FontReader.ReadUInt32())
i += offset
}
}
}
// 解析表 cmap (Unicode编码轮廓索引对照表)
for (Temp in directorys) {
if (Temp.tag == "cmap") {
FontReader.Index = Temp.offset.toInt()
cmap.version = FontReader.ReadUInt16()
cmap.numTables = FontReader.ReadUInt16()
for (i in 0 until cmap.numTables.toInt()) {
val record = CmapRecord()
record.platformID = FontReader.ReadUInt16()
record.encodingID = FontReader.ReadUInt16()
record.offset = FontReader.ReadUInt32()
cmap.records.add(record)
}
for (i in 0 until cmap.numTables.toInt()) {
val fmtOffset = cmap.records[i].offset
FontReader.Index = (Temp.offset + fmtOffset).toInt()
val EndIndex = FontReader.Index
val format = FontReader.ReadUInt16()
if (cmap.tables.contains(fmtOffset.toInt())) continue
when {
format.equals(0) -> {
val fmt = CmapFormat0()
fmt.format = format
fmt.length = FontReader.ReadUInt16()
fmt.language = FontReader.ReadUInt16()
fmt.glyphIdArray = FontReader.GetBytes(fmt.length.toInt() - 6)
cmap.tables[fmtOffset] = fmt
}
format.equals(4) -> {
val fmt = CmapFormat4()
fmt.format = format
fmt.length = FontReader.ReadUInt16()
fmt.language = FontReader.ReadUInt16()
fmt.segCountX2 = FontReader.ReadUInt16()
val segCount = fmt.segCountX2.toInt() / 2
fmt.searchRange = FontReader.ReadUInt16()
fmt.entrySelector = FontReader.ReadUInt16()
fmt.rangeShift = FontReader.ReadUInt16()
fmt.endCode = FontReader.GetUInt16Array(segCount)
fmt.reservedPad = FontReader.ReadUInt16()
fmt.startCode = FontReader.GetUInt16Array(segCount)
fmt.idDelta = FontReader.GetInt16Array(segCount)
fmt.idRangeOffset = FontReader.GetUInt16Array(segCount)
fmt.glyphIdArray = FontReader.GetUInt16Array((fmt.length.toInt() - (FontReader.Index - EndIndex)) / 2)
cmap.tables[fmtOffset] = fmt
}
format.equals(6) -> {
val fmt = CmapFormat6()
fmt.format = format
fmt.length = FontReader.ReadUInt16()
fmt.language = FontReader.ReadUInt16()
fmt.firstCode = FontReader.ReadUInt16()
fmt.entryCount = FontReader.ReadUInt16()
fmt.glyphIdArray = FontReader.GetUInt16Array(fmt.entryCount.toInt())
cmap.tables[fmtOffset] = fmt
}
format.equals(12) -> {
val fmt = CmapFormat12()
fmt.format = format
fmt.reserved = FontReader.ReadUInt16()
fmt.length = FontReader.ReadUInt32()
fmt.language = FontReader.ReadUInt32()
fmt.numGroups = FontReader.ReadUInt32()
for (n in 0 until fmt.numGroups.toLong()) {
fmt.groups.add(Triple(FontReader.ReadUInt32(), FontReader.ReadUInt32(), FontReader.ReadUInt32()))
}
cmap.tables[fmtOffset] = fmt
}
}
}
}
}
// 解析表 glyf (字体轮廓数据表)
for (Temp in directorys) {
if (Temp.tag == "glyf") {
FontReader.Index = Temp.offset.toInt()
for (i in 0 until maxp.numGlyphs.toInt()) {
FontReader.Index = (Temp.offset + loca[i]).toInt()
val g = GlyfLayout()
g.numberOfContours = FontReader.ReadInt16()
g.xMin = FontReader.ReadInt16()
g.yMin = FontReader.ReadInt16()
g.xMax = FontReader.ReadInt16()
g.yMax = FontReader.ReadInt16()
if (g.numberOfContours > 0) {
g.endPtsOfContours = FontReader.GetUInt16Array(g.numberOfContours.toInt())
g.instructionLength = FontReader.ReadUInt16()
g.instructions = FontReader.GetBytes(g.instructionLength.toInt())
val flagLength = g.endPtsOfContours.last().toInt() + 1
// 获取轮廓点描述标志
g.flags = ArrayList(flagLength)
var n = 0
while (n < flagLength) {
g.flags[n] = FontReader.GetByte()
if ((g.flags[n].and(0x08)).toInt() != 0x00) {
for (m in FontReader.GetByte() downTo 1) {
g.flags[++n] = g.flags[n - 1]
}
}
++n
}
// 获取轮廓点描述x轴相对值
g.xCoordinates = ArrayList(flagLength)
for (m in 0 until flagLength) {
val same = if ((g.flags[m].and(0x10)).toInt() != 0) 1 else -1
if ((g.flags[m].and(0x02)).toInt() != 0) {
g.xCoordinates[m] = (same * FontReader.GetByte()).toShort()
} else {
g.xCoordinates[m] = if (same == 1) 0 else FontReader.ReadInt16()
}
}
// 获取轮廓点描述y轴相对值
g.yCoordinates = ArrayList(flagLength)
for (m in 0 until flagLength) {
val same = if ((g.flags[m].and(0x20)).toInt() != 0) 1 else -1
if ((g.flags[m].and(0x04)).toInt() != 0) {
g.yCoordinates[n] = (same * FontReader.GetByte()).toShort()
} else {
g.yCoordinates[n] = if (same == 1) 0 else FontReader.ReadInt16()
}
}
/*
// 相对坐标转绝对坐标 (因不绘制字体,这里用不上)
for(m in 1 until flagLength)
{
g.xCoordinates[m] = (g.xCoordinates[m] + g.xCoordinates[m - 1]).toShort()
g.yCoordinates[m] = (g.yCoordinates[m] + g.yCoordinates[m - 1]).toShort()
}
*/
glyf.add(g)
} else {
// 复合字体暂未使用
}
}
}
}
// 建立Unicode&Glyf映射表
for (i in 0..130000) {
val gid = GetGlyfIndex(i).toInt()
if (gid == 0) continue
if (unicodeMap.containsKey(gid)) continue
val thisGlyf = ArrayList<Short>()
thisGlyf.addAll(glyf[gid].xCoordinates)
thisGlyf.addAll(glyf[gid].yCoordinates)
unicodeMap[i] = thisGlyf
}
}
/**
* 获取字体信息 (默认获取字体名称,索引位1)
*/
fun GetNameById(nameId: Int = 1): String {
for (Temp in directorys) {
if (Temp.tag != "name") continue
FontReader.Index = Temp.offset.toInt()
break
}
for (record in name.records) {
if (record.nameID.toInt() != nameId) continue
FontReader.Index = FontReader.Index + (name.stringOffset + record.offset).toInt()
return FontReader.ReadStrings(record.length.toInt(), if (record.platformID.toInt() == 1) Charsets.UTF_8 else Charsets.UTF_16BE)
}
return "error"
}
var pps = arrayListOf<Pair<UShort, UShort>>(Pair(3u, 10u), Pair(0u, 4u), Pair(3u, 1u), Pair(1u, 0u), Pair(0u, 3u), Pair(0u, 1u))
/**
* 使用Unicode值查找轮廓索引
*/
private fun GetGlyfIndex(code: Int): Long {
if (code == 0) return 0
var fmtKey: UInt = 0u
for (item in pps) {
for (record in cmap.records) {
if ((item.first == record.platformID) && (item.second == record.encodingID)) {
fmtKey = record.offset
break
}
}
if (fmtKey > 0u) break
}
if (fmtKey == 0u) return 0
var glyfID: Long = 0
if (cmap.tables[fmtKey] is CmapFormat0) {
val tab = cmap.tables[fmtKey] as CmapFormat0
if (code >= tab.glyphIdArray.size) glyfID = 0
else glyfID = tab.glyphIdArray[code].toLong()
} else if (cmap.tables[fmtKey] is CmapFormat4) {
val tab = cmap.tables[fmtKey] as CmapFormat4
if (code > tab.endCode.last().toInt()) return 0
// 二分法查找数值索引
var start = 0
var middle: Int
var end = tab.endCode.size - 1
while (start + 1 < end) {
middle = (start + end) / 2
if (tab.endCode[middle] <= code.toUInt()) start = middle
else end = middle
}
if (tab.endCode[start] < code.toUInt()) ++start
if (code.toUInt() < tab.startCode[start]) return 0
glyfID = if (tab.idRangeOffset[start].toInt() != 0) {
tab.glyphIdArray[code - tab.startCode[start].toInt() + (tab.idRangeOffset[start].toInt() / 2) - (tab.idRangeOffset.size - start)].toLong()
} else (code + tab.idDelta[start]).toLong()
glyfID = glyfID.and(0xFFFF)
} else if (cmap.tables[fmtKey] is CmapFormat6) {
val tab = cmap.tables[fmtKey] as CmapFormat6
val index = code - tab.firstCode.toInt()
glyfID = if (index < 0 || index >= tab.glyphIdArray.size) 0 else tab.glyphIdArray[index].toLong()
} else if (cmap.tables[fmtKey] is CmapFormat12) {
val tab = (cmap.tables[fmtKey] as CmapFormat12)
if (code > tab.groups.last().second.toInt()) return 0
// 二分法查找数值索引
var start = 0
var middle: Int
var end = tab.groups.size - 1
while (start + 1 < end) {
middle = (start + end) / 2
if (tab.groups[middle].first.toInt() <= code) start = middle
else end = middle
}
if (tab.groups[start].first.toInt() <= code && code <= tab.groups[start].second.toInt()) {
glyfID = (tab.groups[start].third.toInt() + code - tab.groups[start].first.toInt()).toLong()
}
}
return glyfID
}
/**
* 使用轮廓数据获取Unicode值
*/
public fun GetCodeByGlyf(inputGlyf: List<Short>): Int {
var unicodeVal = 0
if (inputGlyf.isEmpty()) return 0
for (g in unicodeMap) {
if (inputGlyf.size != g.value.size) continue
var isFound = true
for (i in inputGlyf.indices) {
if (inputGlyf[i] != g.value[i]) {
isFound = false
break
}
}
if (isFound) unicodeVal = g.key
}
return unicodeVal
}
/**
* 使用Unicode值获取轮廓数据
*/
public fun GetGlyfByCode(code: Int): ArrayList<Short> {
if (code <= 0) return ArrayList()
return unicodeMap.getOrDefault(code, ArrayList())
}
}

@ -118,10 +118,12 @@ object BookContent {
if (correctFont != null && font != null) { if (correctFont != null && font != null) {
val queryTTF = QueryTTF(font!!) val queryTTF = QueryTTF(font!!)
val cQueryTTF = QueryTTF(correctFont!!) val cQueryTTF = QueryTTF(correctFont!!)
val contentArray = contentStr.toStringArray() val contentArray = contentStr.toCharArray()
contentArray.forEachIndexed { index, s -> contentArray.forEachIndexed { index, s ->
if(s> 58000.toChar()){
val code = cQueryTTF.GetCodeByGlyf(queryTTF.GetGlyfByCode(s.toInt())) val code = cQueryTTF.GetCodeByGlyf(queryTTF.GetGlyfByCode(s.toInt()))
contentArray[index] = code.toString() contentArray[index] = code.toChar()
}
} }
contentStr = contentArray.joinToString("") contentStr = contentArray.joinToString("")
} }

Loading…
Cancel
Save