commit
618c155e3c
@ -0,0 +1,60 @@ |
||||
#!/usr/bin/env bash |
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin |
||||
export PATH |
||||
# -------------------------------------------------------------- |
||||
# 系统: ALL |
||||
# 项目: 蓝奏云上传文件 |
||||
# 版本: 1.0.3 |
||||
# 作者: XIU2 |
||||
# 官网: https://shell.xiu2.xyz |
||||
# 项目: https://github.com/XIU2/Shell |
||||
# -------------------------------------------------------------- |
||||
|
||||
USERNAME="admin" # 蓝奏云用户名 |
||||
COOKIE_PHPDISK_INFO="$LANZOU_PSD" # Cookie 中 phpdisk_info 的值 |
||||
COOKIE_YLOGIN="$LANZOU_ID" # Cookie 中 ylogin 的值 |
||||
|
||||
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.7.3670.199 Safari/537.36" |
||||
HEADER_CHECK_LOGIN="User-Agent: ${UA} |
||||
Referer: https://up.woozooo.com/mydisk.php?item=files&action=index&u=${USERNAME} |
||||
Accept-Language: zh-CN,zh;q=0.9" |
||||
|
||||
URL_ACCOUNT="https://pc.woozooo.com/account.php" |
||||
URL_UPLOAD="https://up.woozooo.com/fileup.php" |
||||
|
||||
INFO="[信息]" && ERROR="[错误]" && TIP="[注意]" |
||||
|
||||
# 检查是否已登录 |
||||
_CHECK_LOGIN() { |
||||
if [[ "${COOKIE_PHPDISK_INFO}" = "" || "${COOKIE_PHPDISK_INFO}" = "XXX" ]]; then |
||||
_NOTICE "ERROR" "请指定 Cookie 中 phpdisk_info 的值!" |
||||
fi |
||||
if [[ "${COOKIE_YLOGIN}" = "" || "${COOKIE_YLOGIN}" = "XXX" ]]; then |
||||
_NOTICE "ERROR" "请指定 Cookie 中 ylogin 的值!" |
||||
fi |
||||
|
||||
HTML_CHECK_LOGIN=$(curl -s --http1.1 -b "ylogin=${COOKIE_YLOGIN};phpdisk_info=${COOKIE_PHPDISK_INFO}" -H "${HEADER_CHECK_LOGIN}" "${URL_ACCOUNT}"|grep "登录") |
||||
[[ ! -z "${HTML_CHECK_LOGIN}" ]] && _NOTICE "ERROR" "Cookie 已失效,请更新!" |
||||
} |
||||
|
||||
# 上传文件 |
||||
_UPLOAD() { |
||||
[[ $(du "${NAME_FILE}"|awk '{print $1}') -gt 100000000 ]] && _NOTICE "ERROR" "${NAME}文件大于 100MB!" |
||||
HTML_UPLOAD=$(curl --connect-timeout 120 -m 5000 --retry 2 -s -b "ylogin=${COOKIE_YLOGIN};phpdisk_info=${COOKIE_PHPDISK_INFO}" -H "${URL_UPLOAD}" -F "task=1" -F "id=WU_FILE_0" -F "folder_id=${FOLDER_ID}" -F "name=${NAME}" -F "upload_file=@${NAME_FILE}" "${URL_UPLOAD}"|grep '\\u4e0a\\u4f20\\u6210\\u529f') |
||||
[[ -z "${HTML_UPLOAD}" ]] && _NOTICE "ERROR" "${NAME}文件上传失败!" |
||||
echo -e "${INFO} 文件上传成功![$(date -u -d '+8 hour' '+%Y-%m-%d %H:%M:%S')]" |
||||
} |
||||
|
||||
|
||||
NAME="$1" # 文件名 |
||||
NAME_FILE="$2" # 文件路径 |
||||
FOLDER_ID="$3" # 上传文件夹ID |
||||
if [[ -z "${NAME}" ]]; then |
||||
echo -e "${ERROR} 未指定文件名!" && exit 1 |
||||
elif [[ -z "${NAME_FILE}" ]]; then |
||||
echo -e "${ERROR} 未指定文件路径!" && exit 1 |
||||
elif [[ -z "${FOLDER_ID}" ]]; then |
||||
echo -e "${ERROR} 未指定上传文件夹ID!" && exit 1 |
||||
fi |
||||
_CHECK_LOGIN |
||||
_UPLOAD |
@ -0,0 +1,6 @@ |
||||
# 替换管理界面帮助 |
||||
|
||||
* 替换规则是用来替换正文内容的一种规则 |
||||
* 菜单可以新建和导入规则 |
||||
* 可以拖动排序 |
||||
* 可以选择操作 |
@ -1,3 +1,3 @@ |
||||
<!DOCTYPE html><html lang="en" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href="css/about.dbe575e1.css" rel="prefetch"><link href="css/detail.b4324411.css" rel="prefetch"><link href="js/about.d8b0dc91.js" rel="prefetch"><link href="js/about~detail.1caf6ef5.js" rel="prefetch"><link href="js/detail.6a5cc86c.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.ad4ff18f.css" rel="preload" as="style"><link href="js/app.d60dad33.js" rel="preload" as="script"><link href="js/chunk-vendors.060eee33.js" rel="preload" as="script"><link href="css/chunk-vendors.ad4ff18f.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="yd-web-tool"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><style>body::-webkit-scrollbar { |
||||
<!DOCTYPE html><html lang="en" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href="css/about.dbe575e1.css" rel="prefetch"><link href="css/detail.b4324411.css" rel="prefetch"><link href="js/about.4300b5ad.js" rel="prefetch"><link href="js/about~detail.c1b29cbc.js" rel="prefetch"><link href="js/detail.4d0609e7.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.ad4ff18f.css" rel="preload" as="style"><link href="js/app.acf50eba.js" rel="preload" as="script"><link href="js/chunk-vendors.1042cf7f.js" rel="preload" as="script"><link href="css/chunk-vendors.ad4ff18f.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="yd-web-tool"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><style>body::-webkit-scrollbar { |
||||
display: none; |
||||
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.060eee33.js"></script><script src="js/app.d60dad33.js"></script></body></html> |
||||
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.1042cf7f.js"></script><script src="js/app.acf50eba.js"></script></body></html> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,30 @@ |
||||
package io.legado.app.constant |
||||
|
||||
import android.provider.Settings |
||||
import splitties.init.appCtx |
||||
|
||||
val androidId: String by lazy { |
||||
Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) |
||||
} |
||||
|
||||
val appInfo: AppInfo by lazy { |
||||
val appInfo = AppInfo() |
||||
appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)?.let { |
||||
appInfo.versionName = it.versionName |
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { |
||||
appInfo.versionCode = it.longVersionCode |
||||
} else { |
||||
@Suppress("DEPRECATION") |
||||
appInfo.versionCode = it.versionCode.toLong() |
||||
} |
||||
} |
||||
appInfo |
||||
} |
||||
|
||||
val charsets = |
||||
arrayListOf("UTF-8", "GB2312", "GB18030", "GBK", "Unicode", "UTF-16", "UTF-16LE", "ASCII") |
||||
|
||||
data class AppInfo( |
||||
var versionCode: Long = 0L, |
||||
var versionName: String = "" |
||||
) |
@ -0,0 +1,32 @@ |
||||
package io.legado.app.data.dao |
||||
|
||||
import androidx.room.* |
||||
import io.legado.app.data.entities.EpubChapter |
||||
|
||||
@Dao |
||||
interface EpubChapterDao { |
||||
@get:Query("select * from epubChapters") |
||||
val all: List<EpubChapter> |
||||
|
||||
@Query("select count(*) from epubChapters Where bookUrl = :bookUrl") |
||||
fun getCnt(bookUrl: String): Int |
||||
|
||||
@Query("select * from epubChapters Where bookUrl = :bookUrl and parentHref = :parentHref ") |
||||
fun get(bookUrl: String, parentHref: String): List<EpubChapter> |
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE) |
||||
fun insert(vararg chapter: EpubChapter) |
||||
|
||||
@Query("delete from epubChapters") |
||||
fun clear() |
||||
|
||||
@Query("delete from epubChapters Where bookUrl = :bookUrl") |
||||
fun deleteByName(bookUrl: String) |
||||
|
||||
@Delete |
||||
fun delete(vararg chapter: EpubChapter) |
||||
|
||||
@Update |
||||
fun update(vararg chapter: EpubChapter) |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
package io.legado.app.data.entities |
||||
|
||||
import androidx.room.Entity |
||||
import androidx.room.ForeignKey |
||||
import androidx.room.Index |
||||
|
||||
@Entity( |
||||
tableName = "epubChapters", |
||||
primaryKeys = ["bookUrl", "href"], |
||||
indices = [(Index(value = ["bookUrl"], unique = false)), |
||||
(Index(value = ["bookUrl", "href"], unique = true))], |
||||
foreignKeys = [(ForeignKey( |
||||
entity = Book::class, |
||||
parentColumns = ["bookUrl"], |
||||
childColumns = ["bookUrl"], |
||||
onDelete = ForeignKey.CASCADE |
||||
))] |
||||
) |
||||
data class EpubChapter( |
||||
var bookUrl: String = "", |
||||
var href: String = "", |
||||
var parentHref: String? = null, |
||||
) |
@ -0,0 +1,600 @@ |
||||
package io.legado.app.model.analyzeRule; |
||||
|
||||
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 static class Header { |
||||
public int majorVersion; |
||||
public int minorVersion; |
||||
public int numOfTables; |
||||
public int searchRange; |
||||
public int entrySelector; |
||||
public int rangeShift; |
||||
} |
||||
|
||||
private static 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 static class NameLayout { |
||||
public int format; |
||||
public int count; |
||||
public int stringOffset; |
||||
public List<NameRecord> records = new LinkedList<>(); |
||||
} |
||||
|
||||
private static 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 static 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 static 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 static class CmapRecord { |
||||
public int platformID; |
||||
public int encodingID; |
||||
public int offset; |
||||
} |
||||
|
||||
private static class CmapFormat { |
||||
public int format; |
||||
public int length; |
||||
public int language; |
||||
public byte[] glyphIdArray; |
||||
} |
||||
|
||||
private static 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 static class CmapFormat6 extends CmapFormat { |
||||
public int firstCode; |
||||
public int entryCount; |
||||
public int[] glyphIdArray; |
||||
} |
||||
|
||||
private static class CmapFormat12 extends CmapFormat { |
||||
public int reserved; |
||||
public int length; |
||||
public int language; |
||||
public int numGroups; |
||||
public List<Triple<Integer, Integer, Integer>> groups; |
||||
} |
||||
|
||||
private static 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 static 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 int ReadUInt32() { |
||||
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 Pair[] 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) |
||||
}; |
||||
|
||||
public final Map<Integer, String> CodeToGlyph = new HashMap<>(); |
||||
public final Map<String, Integer> GlyphToCode = new HashMap<>(); |
||||
private int LimitMix = 0; |
||||
private int LimitMax = 0; |
||||
|
||||
/** |
||||
* 构造函数 |
||||
* |
||||
* @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&Glyph双向表
|
||||
for (int key = 0; key < 130000; ++key) { |
||||
if (key == 0xFF) key = 0x3400; |
||||
int gid = getGlyfIndex(key); |
||||
if (gid == 0) 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 == 0) LimitMix = key; |
||||
LimitMax = key; |
||||
CodeToGlyph.put(key, val); |
||||
if (GlyphToCode.containsKey(val)) continue; |
||||
GlyphToCode.put(val, key); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取字体信息 (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 (@SuppressWarnings("unchecked") 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 = 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 code 传入Unicode十进制值 |
||||
* @return 返回bool查询结果 |
||||
*/ |
||||
public boolean inLimit(char code) { |
||||
return (LimitMix <= code) && (code < LimitMax); |
||||
} |
||||
|
||||
/** |
||||
* 使用Unicode值获取轮廓数据 |
||||
* |
||||
* @param key 传入Unicode十进制值 |
||||
* @return 返回轮廓数组的String值 |
||||
*/ |
||||
public String getGlyfByCode(int key) { |
||||
return CodeToGlyph.getOrDefault(key, ""); |
||||
} |
||||
|
||||
/** |
||||
* 使用轮廓数据获取Unicode值 |
||||
* |
||||
* @param val 传入轮廓数组的String值 |
||||
* @return 返回Unicode十进制值 |
||||
*/ |
||||
public int getCodeByGlyf(String val) { |
||||
return GlyphToCode.getOrDefault(val, 0); |
||||
} |
||||
} |
@ -1,633 +0,0 @@ |
||||
package io.legado.app.model.analyzeRule |
||||
|
||||
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.* |
||||
import kotlin.experimental.and |
||||
|
||||
class QueryTTF(buffer: ByteArray) { |
||||
private class Header( |
||||
var majorVersion: Int = 0, |
||||
var minorVersion: Int = 0, |
||||
var numOfTables: Int = 0, |
||||
var searchRange: Int = 0, |
||||
var entrySelector: Int = 0, |
||||
var rangeShift: Int = 0, |
||||
) |
||||
|
||||
private class Directory { |
||||
lateinit var tag: String // table name |
||||
var checkSum: Int = 0 // Check sum |
||||
var offset: Int = 0 // Offset from beginning of file |
||||
var length: Int = 0 // length of the table in bytes |
||||
} |
||||
|
||||
private class NameLayout( |
||||
var format: Int = 0, |
||||
var count: Int = 0, |
||||
var stringOffset: Int = 0, |
||||
var records: MutableList<NameRecord> = LinkedList(), |
||||
) |
||||
|
||||
private class NameRecord( |
||||
var platformID: Int = 0, // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom> |
||||
var encodingID: Int = 0, // 编码标识符 |
||||
var languageID: Int = 0, // 语言标识符 |
||||
var nameID: Int = 0, // 名称标识符 |
||||
var length: Int = 0,// 名称字符串的长度 |
||||
var offset: Int = 0, // 名称字符串相对于stringOffset的字节偏移量 |
||||
) |
||||
|
||||
private class HeadLayout( |
||||
var majorVersion: Int = 0, |
||||
var minorVersion: Int = 0, |
||||
var fontRevision: Int = 0, |
||||
var checkSumAdjustment: Int = 0, |
||||
var magicNumber: Int = 0, |
||||
var flags: Int = 0, |
||||
var unitsPerEm: Int = 0, |
||||
var created: Long = 0, |
||||
var modified: Long = 0, |
||||
var xMin: Short = 0, |
||||
var yMin: Short = 0, |
||||
var xMax: Short = 0, |
||||
var yMax: Short = 0, |
||||
var macStyle: Int = 0, |
||||
var lowestRecPPEM: Int = 0, |
||||
var fontDirectionHint: Short = 0, |
||||
var indexToLocFormat: Short = 0, // <0:loca是2字节数组, 1:loca是4字节数组> |
||||
var glyphDataFormat: Short = 0, |
||||
) |
||||
|
||||
private class MaxpLayout( |
||||
var majorVersion: Int = 0, |
||||
var minorVersion: Int = 0, |
||||
var numGlyphs: Int = 0, // 字体中的字形数量 |
||||
var maxPoints: Int = 0, |
||||
var maxContours: Int = 0, |
||||
var maxCompositePoints: Int = 0, |
||||
var maxCompositeContours: Int = 0, |
||||
var maxZones: Int = 0, |
||||
var maxTwilightPoints: Int = 0, |
||||
var maxStorage: Int = 0, |
||||
var maxFunctionDefs: Int = 0, |
||||
var maxInstructionDefs: Int = 0, |
||||
var maxStackElements: Int = 0, |
||||
var maxSizeOfInstructions: Int = 0, |
||||
var maxComponentElements: Int = 0, |
||||
var maxComponentDepth: Int = 0, |
||||
) |
||||
|
||||
private class CmapLayout( |
||||
var version: Int = 0, |
||||
var numTables: Int = 0, |
||||
var records: MutableList<CmapRecord> = LinkedList(), |
||||
var tables: MutableMap<Int, CmapFormat> = HashMap(), |
||||
) |
||||
|
||||
private class CmapRecord( |
||||
var platformID: Int = 0, |
||||
var encodingID: Int = 0, |
||||
var offset: Int = 0, |
||||
) |
||||
|
||||
private open class CmapFormat { |
||||
var format: Int = 0 |
||||
open var length: Int = 0 |
||||
open var language: Int = 0 |
||||
lateinit var glyphIdArray: IntArray |
||||
} |
||||
|
||||
private class CmapFormat4 : CmapFormat() { |
||||
var segCountX2: Int = 0 |
||||
var searchRange: Int = 0 |
||||
var entrySelector: Int = 0 |
||||
var rangeShift: Int = 0 |
||||
lateinit var endCode: IntArray |
||||
var reservedPad: Int = 0 |
||||
lateinit var startCode: IntArray |
||||
lateinit var idDelta: ShortArray |
||||
lateinit var idRangeOffset: IntArray |
||||
// override lateinit var glyphIdArray: IntArray |
||||
} |
||||
|
||||
private class CmapFormat6( |
||||
var firstCode: Int = 0, |
||||
var entryCount: Int = 0, |
||||
) : CmapFormat() |
||||
|
||||
private class CmapFormat12( |
||||
var reserved: Int = 0, |
||||
override var length: Int = 0, |
||||
override var language: Int = 0, |
||||
var numGroups: Int = 0, |
||||
var groups: MutableList<Triple<Int, Int, Int>> |
||||
) : CmapFormat() |
||||
|
||||
private class GlyfLayout( |
||||
var numberOfContours: Short = 0, // 非负值为简单字型,负值为符合字型 |
||||
var xMin: Short = 0, |
||||
var yMin: Short = 0, |
||||
var xMax: Short = 0, |
||||
var yMax: Short = 0, |
||||
var instructionLength: Int = 0 |
||||
) { |
||||
lateinit var endPtsOfContours: IntArray // length=numberOfContours |
||||
lateinit var instructions: IntArray // length=instructionLength |
||||
lateinit var flags: ByteArray |
||||
lateinit var xCoordinates: ShortArray // length = flags.length |
||||
lateinit var yCoordinates: ShortArray // length = flags.length |
||||
} |
||||
|
||||
private class ByteArrayReader(var buffer: ByteArray, var index: Int) { |
||||
fun readUIntX(len: Long): Long { |
||||
var result: Long = 0 |
||||
for (i in 0 until len) { |
||||
result = result shl 8 |
||||
result = result or ((buffer[index++] and 0xFF.toByte()).toLong()) |
||||
} |
||||
return result |
||||
} |
||||
|
||||
fun readUInt64(): Long = readUIntX(8) |
||||
|
||||
fun readUInt32(): Int = readUIntX(4).toInt() |
||||
|
||||
fun readUInt16(): Int = readUIntX(2).toInt() |
||||
|
||||
fun readInt16(): Short = readUIntX(2).toShort() |
||||
|
||||
fun readUInt8(): Short = readUIntX(1).toShort() |
||||
|
||||
fun readStrings(len: Int, charset: Charset): String { |
||||
val result = ByteArray(len) |
||||
for (i in 0 until len) { |
||||
result[i] = buffer[index++] |
||||
} |
||||
return String(result, charset) |
||||
} |
||||
|
||||
fun getByte() = buffer[index++] |
||||
|
||||
fun getBytes(len: Int): IntArray { |
||||
val result = IntArray(len) |
||||
for (i in 0 until len) { |
||||
result[i] = buffer[index++].toInt() |
||||
} |
||||
return result |
||||
} |
||||
|
||||
fun getUInt16Array(len: Int): IntArray { |
||||
val result = IntArray(len) |
||||
for (i in 0 until len) { |
||||
result[i] = readUInt16() |
||||
} |
||||
return result |
||||
} |
||||
|
||||
fun getInt16Array(len: Int): ShortArray { |
||||
val result = ShortArray(len) |
||||
for (i in 0 until len) { |
||||
result[i] = readInt16() |
||||
} |
||||
return result |
||||
} |
||||
} |
||||
|
||||
private val fontReader: ByteArrayReader = ByteArrayReader(buffer, 0) |
||||
private val directories: MutableList<Directory> = LinkedList() |
||||
private val name = NameLayout() |
||||
private val cmap = CmapLayout() |
||||
private val pps: Array<Pair<Int, Int>> = arrayOf( |
||||
Pair.of(3, 10), |
||||
Pair.of(0, 4), |
||||
Pair.of(3, 1), |
||||
Pair.of(1, 0), |
||||
Pair.of(0, 3), |
||||
Pair.of(0, 1) |
||||
) |
||||
private val codeToGlyph: MutableMap<Int, String> = HashMap() |
||||
private val glyphToCode: MutableMap<String, Int> = HashMap() |
||||
private var limitMix = 0 |
||||
private var limitMax = 0 |
||||
|
||||
/** |
||||
* 获取字体信息 (1=字体名称) |
||||
* |
||||
* @param nameId 传入十进制字体信息索引 |
||||
* @return 返回查询结果字符串 |
||||
*/ |
||||
fun getNameById(nameId: Int): String { |
||||
for (temp in directories) { |
||||
if ("name" != temp.tag) { |
||||
continue |
||||
} |
||||
fontReader.index = temp.offset |
||||
break |
||||
} |
||||
for (record in name.records) { |
||||
if (record.nameID != nameId) { |
||||
continue |
||||
} |
||||
fontReader.index += name.stringOffset + record.offset |
||||
return fontReader.readStrings(record.length, if (record.platformID == 1) StandardCharsets.UTF_8 else StandardCharsets.UTF_16BE) |
||||
} |
||||
return "error" |
||||
} |
||||
|
||||
/** |
||||
* 使用Unicode值查找轮廓索引 |
||||
* |
||||
* @param code 传入Unicode十进制值 |
||||
* @return 返回十进制轮廓索引 |
||||
*/ |
||||
private fun getGlyfIndex(code: Int): Int { |
||||
if (code == 0) { |
||||
return 0 |
||||
} |
||||
var fmtKey = 0 |
||||
for (item in pps) { |
||||
for (record in cmap.records) { |
||||
if (item.left == record.platformID && item.right == record.encodingID) { |
||||
fmtKey = record.offset |
||||
break |
||||
} |
||||
} |
||||
if (fmtKey > 0) { |
||||
break |
||||
} |
||||
} |
||||
if (fmtKey == 0) { |
||||
return 0 |
||||
} |
||||
var glyfID = 0 |
||||
val table = cmap.tables[fmtKey] |
||||
when (table?.format) { |
||||
0 -> { |
||||
if (code < table.glyphIdArray.size) { |
||||
glyfID = table.glyphIdArray[code] and 0xFF |
||||
} |
||||
} |
||||
4 -> { |
||||
val tab = table as CmapFormat4 |
||||
if (code > tab.endCode[tab.endCode.size - 1]) { |
||||
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) { |
||||
start = middle |
||||
} else { |
||||
end = middle |
||||
} |
||||
} |
||||
if (tab.endCode[start] < code) { |
||||
++start |
||||
} |
||||
if (code < tab.startCode[start]) { |
||||
return 0 |
||||
} |
||||
glyfID = if (tab.idRangeOffset[start] != 0) { |
||||
tab.glyphIdArray[code - tab.startCode[start] + (tab.idRangeOffset[start] shr 1) - (tab.idRangeOffset.size - start)] |
||||
} else { |
||||
code + tab.idDelta[start] |
||||
} |
||||
glyfID = glyfID and 0xFFFF |
||||
} |
||||
6 -> { |
||||
val tab = table as CmapFormat6 |
||||
val index = code - tab.firstCode |
||||
glyfID = if (index < 0 || index >= tab.glyphIdArray.size) { |
||||
0 |
||||
} else { |
||||
tab.glyphIdArray[index] |
||||
} |
||||
} |
||||
12 -> { |
||||
val tab = table as CmapFormat12 |
||||
if (code > tab.groups[tab.numGroups - 1].middle) { |
||||
return 0 |
||||
} |
||||
// 二分法查找数值索引 |
||||
var start = 0 |
||||
var middle: Int |
||||
var end = tab.numGroups - 1 |
||||
while (start + 1 < end) { |
||||
middle = (start + end) / 2 |
||||
if (tab.groups[middle].left <= code) { |
||||
start = middle |
||||
} else { |
||||
end = middle |
||||
} |
||||
} |
||||
if (tab.groups[start].left <= code && code <= tab.groups[start].middle) { |
||||
glyfID = tab.groups[start].right + code - tab.groups[start].left |
||||
} |
||||
} |
||||
} |
||||
return glyfID |
||||
} |
||||
|
||||
/** |
||||
* 判断Unicode值是否在字体范围内 |
||||
* @param code 传入Unicode十进制值 |
||||
* @return 返回bool查询结果 |
||||
*/ |
||||
fun inLimit(code: Char): Boolean = code.toInt() in limitMix until limitMax |
||||
|
||||
/** |
||||
* 使用Unicode值获取轮廓数据 |
||||
* |
||||
* @param key 传入Unicode十进制值 |
||||
* @return 返回轮廓数组的String值 |
||||
*/ |
||||
fun getGlyfByCode(key: Int): String = codeToGlyph.getOrDefault(key, "") |
||||
|
||||
/** |
||||
* 使用轮廓数据获取Unicode值 |
||||
* |
||||
* @param val 传入轮廓数组的String值 |
||||
* @return 返回Unicode十进制值 |
||||
*/ |
||||
fun getCodeByGlyf(`val`: String): Int = glyphToCode.getOrDefault(`val`, 0) |
||||
|
||||
/** |
||||
* 构造函数 buffer 传入TTF字体二进制数组 |
||||
*/ |
||||
init { |
||||
// 获取文件头 |
||||
val fileHeader = Header() |
||||
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) { |
||||
val d = Directory() |
||||
d.tag = fontReader.readStrings(4, StandardCharsets.US_ASCII) |
||||
d.checkSum = fontReader.readUInt32() |
||||
d.offset = fontReader.readUInt32() |
||||
d.length = fontReader.readUInt32() |
||||
directories.add(d) |
||||
} |
||||
// 解析表 name (字体信息,包含版权、名称、作者等...) |
||||
for (temp in directories) { |
||||
if ("name" == temp.tag) { |
||||
fontReader.index = temp.offset |
||||
name.format = fontReader.readUInt16() |
||||
name.count = fontReader.readUInt16() |
||||
name.stringOffset = fontReader.readUInt16() |
||||
for (i in 0 until name.count) { |
||||
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) |
||||
val head = HeadLayout() |
||||
for (temp in directories) { |
||||
if ("head" == temp.tag) { |
||||
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) |
||||
val maxp = MaxpLayout() |
||||
for (temp in directories) { |
||||
if ("maxp" == temp.tag) { |
||||
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 (轮廓数据偏移地址表) |
||||
val loca: MutableList<Int> = LinkedList() |
||||
for (temp in directories) { |
||||
if ("loca" == temp.tag) { |
||||
fontReader.index = temp.offset |
||||
val offset = if (head.indexToLocFormat == 0.toShort()) 2 else 4 |
||||
var i: Long = 0 |
||||
while (i < temp.length) { |
||||
loca.add(if (offset == 2) fontReader.readUInt16() shl 1 else fontReader.readUInt32()) |
||||
i += offset |
||||
} |
||||
} |
||||
} |
||||
// 解析表 cmap (Unicode编码轮廓索引对照表) |
||||
for (temp in directories) { |
||||
if ("cmap" == temp.tag) { |
||||
fontReader.index = temp.offset |
||||
cmap.version = fontReader.readUInt16() |
||||
cmap.numTables = fontReader.readUInt16() |
||||
for (i in 0 until cmap.numTables) { |
||||
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) { |
||||
val fmtOffset = cmap.records[i].offset |
||||
fontReader.index = temp.offset + fmtOffset |
||||
val endIndex = fontReader.index |
||||
val format = fontReader.readUInt16() |
||||
if (cmap.tables.containsKey(fmtOffset)) { |
||||
continue |
||||
} |
||||
when (format) { |
||||
0 -> { |
||||
val f = CmapFormat() |
||||
f.format = format |
||||
f.length = fontReader.readUInt16() |
||||
f.language = fontReader.readUInt16() |
||||
f.glyphIdArray = fontReader.getBytes(f.length - 6) |
||||
cmap.tables[fmtOffset] = f |
||||
} |
||||
4 -> { |
||||
val f = CmapFormat4() |
||||
f.format = format |
||||
f.length = fontReader.readUInt16() |
||||
f.language = fontReader.readUInt16() |
||||
f.segCountX2 = fontReader.readUInt16() |
||||
val segCount = f.segCountX2 shr 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 shr 1) |
||||
cmap.tables[fmtOffset] = f |
||||
} |
||||
6 -> { |
||||
val f = 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[fmtOffset] = f |
||||
} |
||||
12 -> { |
||||
val f = CmapFormat12( |
||||
reserved = fontReader.readUInt16(), |
||||
length = fontReader.readUInt32(), |
||||
language = fontReader.readUInt32(), |
||||
numGroups = fontReader.readUInt32(), |
||||
groups = ArrayList(fontReader.readUInt32()) |
||||
) |
||||
f.format = format |
||||
for (n in 0 until f.numGroups) { |
||||
(f.groups as ArrayList<Triple<Int, Int, Int>>).add(Triple.of(fontReader.readUInt32(), fontReader.readUInt32(), fontReader.readUInt32())) |
||||
} |
||||
cmap.tables[fmtOffset] = f |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// 解析表 glyf (字体轮廓数据表) |
||||
val glyf: MutableList<GlyfLayout> = LinkedList() |
||||
for (temp in directories) { |
||||
if ("glyf" == temp.tag) { |
||||
fontReader.index = temp.offset |
||||
for (i in 0 until maxp.numGlyphs) { |
||||
fontReader.index = temp.offset + loca[i] |
||||
val numberOfContours = fontReader.readInt16() |
||||
if (numberOfContours > 0) { |
||||
val g = 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.toInt()) |
||||
g.instructionLength = fontReader.readUInt16() |
||||
g.instructions = fontReader.getBytes(g.instructionLength) |
||||
val flagLength = g.endPtsOfContours[g.endPtsOfContours.size - 1] + 1 |
||||
// 获取轮廓点描述标志 |
||||
g.flags = ByteArray(flagLength).apply { |
||||
var n = 0 |
||||
while (n < flagLength) { |
||||
this[n] = fontReader.getByte() |
||||
if ((this[n] and 0x08) != 0x00.toByte()) { |
||||
for (m in fontReader.readUInt8() downTo 1) { |
||||
this[++n] = this[n - 1] |
||||
} |
||||
} |
||||
++n |
||||
} |
||||
} |
||||
// 获取轮廓点描述x轴相对值 |
||||
g.xCoordinates = ShortArray(flagLength) |
||||
for (n in 0 until flagLength) { |
||||
val same = (if (g.flags[n] and 0x10 != 0.toByte()) 1 else -1).toShort() |
||||
if (g.flags[n] and 0x02 != 0.toByte()) { |
||||
g.xCoordinates[n] = (same * fontReader.readUInt8()).toShort() |
||||
} else { |
||||
g.xCoordinates[n] = if (same.toInt() == 1) 0.toShort() else fontReader.readInt16() |
||||
} |
||||
} |
||||
// 获取轮廓点描述y轴相对值 |
||||
g.yCoordinates = ShortArray(flagLength) |
||||
for (n in 0 until flagLength) { |
||||
val same = (if (g.flags[n] and 0x20 != 0.toByte()) 1 else -1).toShort() |
||||
if (g.flags[n] and 0x04 != 0.toByte()) { |
||||
g.yCoordinates[n] = (same * fontReader.readUInt8()).toShort() |
||||
} else { |
||||
g.yCoordinates[n] = if (same.toInt() == 1) 0.toShort() else fontReader.readInt16() |
||||
} |
||||
} |
||||
// 相对坐标转绝对坐标 |
||||
// for (int n = 1; n < flagLength; ++n) { |
||||
// xCoordinates[n] += xCoordinates[n - 1]; |
||||
// yCoordinates[n] += yCoordinates[n - 1]; |
||||
// } |
||||
glyf.add(g) |
||||
} //else情况:复合字体暂未使用 |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 建立Unicode&Glyph双向表 |
||||
var key = 0 |
||||
while (key < 130000) { |
||||
if (key == 0xFF) { |
||||
key = 0x3400 |
||||
} |
||||
val gid = getGlyfIndex(key) |
||||
if (gid == 0) { |
||||
++key |
||||
continue |
||||
} |
||||
val sb = StringBuilder() |
||||
// 字型数据转String,方便存HashMap |
||||
for (b in glyf[gid].xCoordinates) { |
||||
sb.append(b.toInt()) |
||||
} |
||||
for (b in glyf[gid].yCoordinates) { |
||||
sb.append(b.toInt()) |
||||
} |
||||
val `val` = sb.toString() |
||||
if (limitMix == 0) { |
||||
limitMix = key |
||||
} |
||||
limitMax = key |
||||
codeToGlyph[key] = `val` |
||||
if (glyphToCode.containsKey(`val`)) { |
||||
++key |
||||
continue |
||||
} |
||||
glyphToCode[`val`] = key |
||||
++key |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue