mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
Compare commits
25 Commits
fd3d96fa74
...
5126ec0cd2
Author | SHA1 | Date | |
---|---|---|---|
|
5126ec0cd2 | ||
|
38daea8487 | ||
|
67f6af9e3c | ||
|
fbe540d26e | ||
|
16791f4030 | ||
|
d7ac10d05c | ||
|
56e0f4312a | ||
|
b7b97665f2 | ||
|
ffe0e93838 | ||
|
c1e682e66c | ||
|
cb55631ee3 | ||
|
4606ff78f0 | ||
|
c11de0d72f | ||
|
9b5b131ffe | ||
|
3f9c4a1916 | ||
|
2eb2d1f695 | ||
|
5cd0adabc3 | ||
|
de801d61c1 | ||
|
b96b7d4e06 | ||
|
988a1b5385 | ||
|
aad3cee5e5 | ||
|
9d85db5b2d | ||
|
a9fa393f4e | ||
|
2791c00ab4 | ||
|
b55d318d5f |
2
.github/workflows/web.yml
vendored
2
.github/workflows/web.yml
vendored
@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
- name: push changes
|
- name: push changes
|
||||||
if: ${{ github.event_name != 'pull_request' && github.repository == env.UPSTREAM_REPOSITORY }}
|
if: ${{ github.event_name != 'pull_request' && github.repository == env.UPSTREAM_REPOSITORY }}
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5.0.0
|
uses: stefanzweifel/git-auto-commit-action@v5.0.1
|
||||||
with:
|
with:
|
||||||
commit_message: Bump web ${{ env.APP_VER}}
|
commit_message: Bump web ${{ env.APP_VER}}
|
||||||
file_pattern: app/src/main/assets/web/vue/
|
file_pattern: app/src/main/assets/web/vue/
|
||||||
|
@ -219,9 +219,9 @@ dependencies {
|
|||||||
implementation(libs.json.path)
|
implementation(libs.json.path)
|
||||||
implementation(libs.jsoupxpath)
|
implementation(libs.jsoupxpath)
|
||||||
implementation(project(path: ':modules:book'))
|
implementation(project(path: ':modules:book'))
|
||||||
|
implementation implementation(project(path: ':modules:rhino'))
|
||||||
|
|
||||||
//JS rhino
|
//JS rhino
|
||||||
implementation(project(path: ':modules:rhino1.7.3'))
|
|
||||||
|
|
||||||
//网络
|
//网络
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
|
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@ -223,6 +223,9 @@
|
|||||||
-keep class com.sun.script.javascript.** { *; }
|
-keep class com.sun.script.javascript.** { *; }
|
||||||
-keep class org.mozilla.** { *; }
|
-keep class org.mozilla.** { *; }
|
||||||
-dontwarn org.mozilla.javascript.engine.RhinoScriptEngineFactory
|
-dontwarn org.mozilla.javascript.engine.RhinoScriptEngineFactory
|
||||||
|
### 以下内容是更新rhino1.7.14.jar后IDE提示添加的
|
||||||
|
-dontwarn java.beans.**
|
||||||
|
-dontwarn javax.script.**
|
||||||
|
|
||||||
## EPUB
|
## EPUB
|
||||||
-dontwarn nl.siegmann.epublib.**
|
-dontwarn nl.siegmann.epublib.**
|
||||||
@ -270,4 +273,3 @@
|
|||||||
-keep class * extends java.lang.Exception
|
-keep class * extends java.lang.Exception
|
||||||
-keep class * extends java.lang.Error
|
-keep class * extends java.lang.Error
|
||||||
-keep class **Exception
|
-keep class **Exception
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ deleteFile(path: String)
|
|||||||
|
|
||||||
> 其他没有添加的算法可在JavaScript中使用`JavaImporter`[调用](https://m.jb51.net/article/92138.htm)Java,例子可参考`朗读引擎-阿里云语音`
|
> 其他没有添加的算法可在JavaScript中使用`JavaImporter`[调用](https://m.jb51.net/article/92138.htm)Java,例子可参考`朗读引擎-阿里云语音`
|
||||||
|
|
||||||
> 注意阅读app会屏蔽部分java类调用,见[RhinoClassShutter](https://github.com/gedoor/legado/blob/master/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoClassShutter.kt)
|
> 注意阅读app会屏蔽部分java类调用,见[RhinoClassShutter](https://github.com/gedoor/legado/blob/master/modules/rhino/src/main/java/com/script/rhino/RhinoClassShutter.kt)
|
||||||
|
|
||||||
> 注意:如果输入的参数不是Utf8String 可先调用`java.hexDecodeToByteArray java.base64DecodeToByteArray`转成ByteArray
|
> 注意:如果输入的参数不是Utf8String 可先调用`java.hexDecodeToByteArray java.base64DecodeToByteArray`转成ByteArray
|
||||||
* 对称加密
|
* 对称加密
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
app/src/main/assets/web/vue/assets/config-Fs1T4bSS.js
Normal file
1
app/src/main/assets/web/vue/assets/config-Fs1T4bSS.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{A as f,u as n}from"./index-Dc0D4E2k.js";import"./vendor-DX7Tq5eS.js";f.getReadConfig().then(e=>{var t=e.data.data;if(t){const a=n();let o=JSON.parse(t),i=a.config;o=Object.assign(i,o),a.setConfig(o)}});
|
@ -1 +0,0 @@
|
|||||||
import{A as f,u as n}from"./index-eiEvjBAi.js";import"./vendor-CNdhk-0s.js";f.getReadConfig().then(e=>{var t=e.data.data;if(t){const a=n();let o=JSON.parse(t),i=a.config;o=Object.assign(i,o),a.setConfig(o)}});
|
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
import{A as L,u as D,z as v,K as y,a6 as Y,ae as b}from"./vendor-CNdhk-0s.js";import{u as k}from"./index-eiEvjBAi.js";function H(e){return typeof e=="function"?e():D(e)}typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const w=()=>{};function W(e,r){function t(...o){return new Promise((a,l)=>{Promise.resolve(e(()=>r.apply(this,o),{fn:r,thisArg:this,args:o})).then(a).catch(l)})}return t}function A(...e){let r=0,t,o=!0,a=w,l,i,s,n,c;!L(e[0])&&typeof e[0]=="object"?{delay:i,trailing:s=!0,leading:n=!0,rejectOnCancel:c=!1}=e[0]:[i,s=!0,n=!0,c=!1]=e;const p=()=>{t&&(clearTimeout(t),t=void 0,a(),a=w)};return d=>{const u=H(i),g=Date.now()-r,m=()=>l=d();return p(),u<=0?(r=Date.now(),m()):(g>u&&(n||!o)?(r=Date.now(),m()):s&&(l=new Promise((h,M)=>{a=c?M:h,t=setTimeout(()=>{r=Date.now(),o=!0,h(m()),p()},Math.max(0,u-g))})),!n&&!t&&(t=setTimeout(()=>o=!0,u)),o=!1,l)}}function C(e,r=200,t=!1,o=!0,a=!1){return W(A(r,t,o,a),e)}const T=/[YMDHhms]o|\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|SSS/g;function $(e,r,t,o){let a=e<12?"AM":"PM";return o&&(a=a.split("").reduce((l,i)=>l+=`${i}.`,"")),t?a.toLowerCase():a}function f(e){const r=["th","st","nd","rd"],t=e%100;return e+(r[(t-20)%10]||r[t]||r[0])}function x(e,r,t={}){var o;const a=e.getFullYear(),l=e.getMonth(),i=e.getDate(),s=e.getHours(),n=e.getMinutes(),c=e.getSeconds(),p=e.getMilliseconds(),S=e.getDay(),d=(o=t.customMeridiem)!=null?o:$,u={Yo:()=>f(a),YY:()=>String(a).slice(-2),YYYY:()=>a,M:()=>l+1,Mo:()=>f(l+1),MM:()=>`${l+1}`.padStart(2,"0"),MMM:()=>e.toLocaleDateString(t.locales,{month:"short"}),MMMM:()=>e.toLocaleDateString(t.locales,{month:"long"}),D:()=>String(i),Do:()=>f(i),DD:()=>`${i}`.padStart(2,"0"),H:()=>String(s),Ho:()=>f(s),HH:()=>`${s}`.padStart(2,"0"),h:()=>`${s%12||12}`.padStart(1,"0"),ho:()=>f(s%12||12),hh:()=>`${s%12||12}`.padStart(2,"0"),m:()=>String(n),mo:()=>f(n),mm:()=>`${n}`.padStart(2,"0"),s:()=>String(c),so:()=>f(c),ss:()=>`${c}`.padStart(2,"0"),SSS:()=>`${p}`.padStart(3,"0"),d:()=>S,dd:()=>e.toLocaleDateString(t.locales,{weekday:"narrow"}),ddd:()=>e.toLocaleDateString(t.locales,{weekday:"short"}),dddd:()=>e.toLocaleDateString(t.locales,{weekday:"long"}),A:()=>d(s,n),AA:()=>d(s,n,!1,!0),a:()=>d(s,n,!0),aa:()=>d(s,n,!0,!0)};return r.replace(T,(g,m)=>{var h,M;return(M=m??((h=u[g])==null?void 0:h.call(u)))!=null?M:g})}const E=e=>/,\s*\{/.test(e)||!(e.startsWith("http")||e.startsWith("data:")||e.startsWith("blob:"));function U(e){return location.origin+"/image?path="+encodeURIComponent(e)+"&url="+encodeURIComponent(sessionStorage.getItem("bookUrl"))+"&width="+k().config.readWidth}const z=e=>{let r=new Date().getTime(),t=Math.floor((r-e)/1e3),o="";return t<=30?o="刚刚":t<60?o=t+"秒前":t<3600?o=Math.floor(t/60)+"分钟前":t<86400?o=Math.floor(t/3600)+"小时前":t<2592e3?o=Math.floor(t/86400)+"天前":o=x(new Date(e),"YYYY-MM-DD"),o},F='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248zm452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248zM828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0z"/></svg>',V=(e,r,t=F)=>{const o=v(!1);let a=null;const l=()=>o.value=!1,i=()=>o.value=!0;y(o,n=>{if(!n)return a==null?void 0:a.close();a=b.service({target:D(e),spinner:t,text:r,lock:!0,background:"rgba(0, 0, 0, 0)"})});const s=n=>{if(!(n instanceof Promise))throw TypeError("loadingWrapper argument must be Promise");return i(),n.finally(l)};return Y(()=>{l()}),{isLoading:o,showLoading:i,closeLoading:l,loadingWrapper:s}};export{C as a,z as d,U as g,E as i,V as u};
|
import{A as L,u as D,z as v,K as y,a6 as Y,ae as b}from"./vendor-DX7Tq5eS.js";import{u as k}from"./index-Dc0D4E2k.js";function H(e){return typeof e=="function"?e():D(e)}typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const w=()=>{};function W(e,r){function t(...o){return new Promise((a,l)=>{Promise.resolve(e(()=>r.apply(this,o),{fn:r,thisArg:this,args:o})).then(a).catch(l)})}return t}function A(...e){let r=0,t,o=!0,a=w,l,i,s,n,c;!L(e[0])&&typeof e[0]=="object"?{delay:i,trailing:s=!0,leading:n=!0,rejectOnCancel:c=!1}=e[0]:[i,s=!0,n=!0,c=!1]=e;const p=()=>{t&&(clearTimeout(t),t=void 0,a(),a=w)};return d=>{const u=H(i),g=Date.now()-r,m=()=>l=d();return p(),u<=0?(r=Date.now(),m()):(g>u&&(n||!o)?(r=Date.now(),m()):s&&(l=new Promise((h,M)=>{a=c?M:h,t=setTimeout(()=>{r=Date.now(),o=!0,h(m()),p()},Math.max(0,u-g))})),!n&&!t&&(t=setTimeout(()=>o=!0,u)),o=!1,l)}}function C(e,r=200,t=!1,o=!0,a=!1){return W(A(r,t,o,a),e)}const T=/[YMDHhms]o|\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|SSS/g;function $(e,r,t,o){let a=e<12?"AM":"PM";return o&&(a=a.split("").reduce((l,i)=>l+=`${i}.`,"")),t?a.toLowerCase():a}function f(e){const r=["th","st","nd","rd"],t=e%100;return e+(r[(t-20)%10]||r[t]||r[0])}function x(e,r,t={}){var o;const a=e.getFullYear(),l=e.getMonth(),i=e.getDate(),s=e.getHours(),n=e.getMinutes(),c=e.getSeconds(),p=e.getMilliseconds(),S=e.getDay(),d=(o=t.customMeridiem)!=null?o:$,u={Yo:()=>f(a),YY:()=>String(a).slice(-2),YYYY:()=>a,M:()=>l+1,Mo:()=>f(l+1),MM:()=>`${l+1}`.padStart(2,"0"),MMM:()=>e.toLocaleDateString(t.locales,{month:"short"}),MMMM:()=>e.toLocaleDateString(t.locales,{month:"long"}),D:()=>String(i),Do:()=>f(i),DD:()=>`${i}`.padStart(2,"0"),H:()=>String(s),Ho:()=>f(s),HH:()=>`${s}`.padStart(2,"0"),h:()=>`${s%12||12}`.padStart(1,"0"),ho:()=>f(s%12||12),hh:()=>`${s%12||12}`.padStart(2,"0"),m:()=>String(n),mo:()=>f(n),mm:()=>`${n}`.padStart(2,"0"),s:()=>String(c),so:()=>f(c),ss:()=>`${c}`.padStart(2,"0"),SSS:()=>`${p}`.padStart(3,"0"),d:()=>S,dd:()=>e.toLocaleDateString(t.locales,{weekday:"narrow"}),ddd:()=>e.toLocaleDateString(t.locales,{weekday:"short"}),dddd:()=>e.toLocaleDateString(t.locales,{weekday:"long"}),A:()=>d(s,n),AA:()=>d(s,n,!1,!0),a:()=>d(s,n,!0),aa:()=>d(s,n,!0,!0)};return r.replace(T,(g,m)=>{var h,M;return(M=m??((h=u[g])==null?void 0:h.call(u)))!=null?M:g})}const E=e=>/,\s*\{/.test(e)||!(e.startsWith("http")||e.startsWith("data:")||e.startsWith("blob:"));function U(e){return location.origin+"/image?path="+encodeURIComponent(e)+"&url="+encodeURIComponent(sessionStorage.getItem("bookUrl"))+"&width="+k().config.readWidth}const z=e=>{let r=new Date().getTime(),t=Math.floor((r-e)/1e3),o="";return t<=30?o="刚刚":t<60?o=t+"秒前":t<3600?o=Math.floor(t/60)+"分钟前":t<86400?o=Math.floor(t/3600)+"小时前":t<2592e3?o=Math.floor(t/86400)+"天前":o=x(new Date(e),"YYYY-MM-DD"),o},F='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248zm452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248zM828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0z"/></svg>',V=(e,r,t=F)=>{const o=v(!1);let a=null;const l=()=>o.value=!1,i=()=>o.value=!0;y(o,n=>{if(!n)return a==null?void 0:a.close();a=b.service({target:D(e),spinner:t,text:r,lock:!0,background:"rgba(0, 0, 0, 0)"})});const s=n=>{if(!(n instanceof Promise))throw TypeError("loadingWrapper argument must be Promise");return i(),n.finally(l)};return Y(()=>{l()}),{isLoading:o,showLoading:i,closeLoading:l,loadingWrapper:s}};export{C as a,z as d,U as g,E as i,V as u};
|
File diff suppressed because one or more lines are too long
50
app/src/main/assets/web/vue/assets/vendor-DX7Tq5eS.js
Normal file
50
app/src/main/assets/web/vue/assets/vendor-DX7Tq5eS.js
Normal file
File diff suppressed because one or more lines are too long
@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="./favicon.ico" />
|
<link rel="icon" href="./favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<script type="module" crossorigin src="./assets/index-eiEvjBAi.js"></script>
|
<script type="module" crossorigin src="./assets/index-Dc0D4E2k.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="./assets/vendor-CNdhk-0s.js">
|
<link rel="modulepreload" crossorigin href="./assets/vendor-DX7Tq5eS.js">
|
||||||
<link rel="stylesheet" crossorigin href="./assets/vendor-B7p0W10y.css">
|
<link rel="stylesheet" crossorigin href="./assets/vendor-B7p0W10y.css">
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-DT4CNe0X.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-DT4CNe0X.css">
|
||||||
</head>
|
</head>
|
||||||
|
@ -775,7 +775,7 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
}
|
}
|
||||||
font ?: return null
|
font ?: return null
|
||||||
qTTF = QueryTTF(font)
|
qTTF = QueryTTF(font)
|
||||||
CacheManager.put(key, qTTF)
|
CacheManager.put(key, qTTF) // debug注释掉
|
||||||
return qTTF
|
return qTTF
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AppLog.put("获取字体处理类出错", e)
|
AppLog.put("获取字体处理类出错", e)
|
||||||
@ -798,15 +798,21 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节
|
val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节
|
||||||
contentArray.forEachIndexed { index, s ->
|
contentArray.forEachIndexed { index, s ->
|
||||||
val oldCode = s.codePointAt(0)
|
val oldCode = s.codePointAt(0)
|
||||||
val glyf = errorQueryTTF.getGlyfByCode(oldCode)
|
// 忽略正常的空白字符
|
||||||
val code = correctQueryTTF.getCodeByGlyf(glyf)
|
if (errorQueryTTF.isBlankUnicode(oldCode)) {
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
val glyf = errorQueryTTF.getGlyfByUnicode(oldCode)
|
||||||
|
// 删除轮廓数据不存在的字符
|
||||||
|
if (filter && (glyf == null)) {
|
||||||
|
contentArray[index] = ""
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
// 使用轮廓数据反查Unicode
|
||||||
|
val code = correctQueryTTF.getUnicodeByGlyf(glyf)
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
contentArray[index] = code.toChar().toString()
|
contentArray[index] = code.toChar().toString()
|
||||||
}
|
}
|
||||||
if (glyf == "" && filter) {
|
|
||||||
// 删除轮廓数据为空的字符
|
|
||||||
contentArray[index] = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return contentArray.joinToString("")
|
return contentArray.joinToString("")
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,68 @@
|
|||||||
package io.legado.app.model.analyzeRule;
|
package io.legado.app.model.analyzeRule;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||||
public class QueryTTF {
|
public class QueryTTF {
|
||||||
|
/**
|
||||||
|
* 文件头
|
||||||
|
*
|
||||||
|
* @url <a href="https://learn.microsoft.com/zh-cn/typography/opentype/spec/otff">Microsoft opentype 字体文档</a>
|
||||||
|
*/
|
||||||
private static class Header {
|
private static class Header {
|
||||||
public int majorVersion;
|
/**
|
||||||
public int minorVersion;
|
* uint32 字体版本 0x00010000 (ttf)
|
||||||
public int numOfTables;
|
*/
|
||||||
|
public long sfntVersion;
|
||||||
|
/**
|
||||||
|
* uint16 Number of tables.
|
||||||
|
*/
|
||||||
|
public int numTables;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int searchRange;
|
public int searchRange;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int entrySelector;
|
public int entrySelector;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int rangeShift;
|
public int rangeShift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据表目录
|
||||||
|
*/
|
||||||
private static class Directory {
|
private static class Directory {
|
||||||
public String tag; // table name
|
/**
|
||||||
public int checkSum; // Check sum
|
* uint32 (表标识符)
|
||||||
public int offset; // Offset from beginning of file
|
*/
|
||||||
public int length; // length of the table in bytes
|
public String tableTag;
|
||||||
|
/**
|
||||||
|
* uint32 (该表的校验和)
|
||||||
|
*/
|
||||||
|
public int checkSum;
|
||||||
|
/**
|
||||||
|
* uint32 (TTF文件 Bytes 数据索引 0 开始的偏移地址)
|
||||||
|
*/
|
||||||
|
public int offset;
|
||||||
|
/**
|
||||||
|
* uint32 (该表的长度)
|
||||||
|
*/
|
||||||
|
public int length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NameLayout {
|
private static class NameLayout {
|
||||||
public int format;
|
public int format;
|
||||||
public int count;
|
public int count;
|
||||||
public int stringOffset;
|
public int stringOffset;
|
||||||
public List<NameRecord> records = new LinkedList<>();
|
public LinkedList<NameRecord> records = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NameRecord {
|
private static class NameRecord {
|
||||||
@ -49,113 +74,439 @@ public class QueryTTF {
|
|||||||
public int offset; // 名称字符串相对于stringOffset的字节偏移量
|
public int offset; // 名称字符串相对于stringOffset的字节偏移量
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font Header Table
|
||||||
|
*/
|
||||||
private static class HeadLayout {
|
private static class HeadLayout {
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int majorVersion;
|
public int majorVersion;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int minorVersion;
|
public int minorVersion;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int fontRevision;
|
public int fontRevision;
|
||||||
|
/**
|
||||||
|
* uint32
|
||||||
|
*/
|
||||||
public int checkSumAdjustment;
|
public int checkSumAdjustment;
|
||||||
|
/**
|
||||||
|
* uint32
|
||||||
|
*/
|
||||||
public int magicNumber;
|
public int magicNumber;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int flags;
|
public int flags;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int unitsPerEm;
|
public int unitsPerEm;
|
||||||
|
/**
|
||||||
|
* long
|
||||||
|
*/
|
||||||
public long created;
|
public long created;
|
||||||
|
/**
|
||||||
|
* long
|
||||||
|
*/
|
||||||
public long modified;
|
public long modified;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short xMin;
|
public short xMin;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short yMin;
|
public short yMin;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short xMax;
|
public short xMax;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short yMax;
|
public short yMax;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int macStyle;
|
public int macStyle;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int lowestRecPPEM;
|
public int lowestRecPPEM;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short fontDirectionHint;
|
public short fontDirectionHint;
|
||||||
public short indexToLocFormat; // <0:loca是2字节数组, 1:loca是4字节数组>
|
/**
|
||||||
|
* int16
|
||||||
|
* <p> 0 表示短偏移 (Offset16),1 表示长偏移 (Offset32)。
|
||||||
|
*/
|
||||||
|
public short indexToLocFormat;
|
||||||
|
/**
|
||||||
|
* int16
|
||||||
|
*/
|
||||||
public short glyphDataFormat;
|
public short glyphDataFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum Profile
|
||||||
|
*/
|
||||||
private static class MaxpLayout {
|
private static class MaxpLayout {
|
||||||
public int majorVersion;
|
/**
|
||||||
public int minorVersion;
|
* uint32 高16位表示整数,低16位表示小数
|
||||||
public int numGlyphs; // 字体中的字形数量
|
*/
|
||||||
|
public int version;
|
||||||
|
/**
|
||||||
|
* uint16 字体中的字形数量
|
||||||
|
*/
|
||||||
|
public int numGlyphs;
|
||||||
|
/**
|
||||||
|
* uint16 非复合字形中包含的最大点数。点是构成字形轮廓的基本单位。
|
||||||
|
*/
|
||||||
public int maxPoints;
|
public int maxPoints;
|
||||||
|
/**
|
||||||
|
* uint16 非复合字形中包含的最大轮廓数。轮廓是由一系列点连接形成的封闭曲线。
|
||||||
|
*/
|
||||||
public int maxContours;
|
public int maxContours;
|
||||||
|
/**
|
||||||
|
* uint16 复合字形中包含的最大点数。复合字形是由多个简单字形组合而成的。
|
||||||
|
*/
|
||||||
public int maxCompositePoints;
|
public int maxCompositePoints;
|
||||||
|
/**
|
||||||
|
* uint16 复合字形中包含的最大轮廓数。
|
||||||
|
*/
|
||||||
public int maxCompositeContours;
|
public int maxCompositeContours;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxZones;
|
public int maxZones;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxTwilightPoints;
|
public int maxTwilightPoints;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxStorage;
|
public int maxStorage;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxFunctionDefs;
|
public int maxFunctionDefs;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxInstructionDefs;
|
public int maxInstructionDefs;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxStackElements;
|
public int maxStackElements;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int maxSizeOfInstructions;
|
public int maxSizeOfInstructions;
|
||||||
|
/**
|
||||||
|
* uint16 任何复合字形在“顶层”引用的最大组件数。
|
||||||
|
*/
|
||||||
public int maxComponentElements;
|
public int maxComponentElements;
|
||||||
|
/**
|
||||||
|
* uint16 递归的最大层数;简单组件为1。
|
||||||
|
*/
|
||||||
public int maxComponentDepth;
|
public int maxComponentDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符到字形索引映射表
|
||||||
|
*/
|
||||||
private static class CmapLayout {
|
private static class CmapLayout {
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
public int version;
|
public int version;
|
||||||
|
/**
|
||||||
|
* uint16 后面的编码表的数量
|
||||||
|
*/
|
||||||
public int numTables;
|
public int numTables;
|
||||||
public List<CmapRecord> records = new LinkedList<>();
|
public LinkedList<CmapRecord> records = new LinkedList<>();
|
||||||
public Map<Integer, CmapFormat> tables = new HashMap<>();
|
public HashMap<Integer, CmapFormat> tables = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encoding records and encodings
|
||||||
|
*/
|
||||||
private static class CmapRecord {
|
private static class CmapRecord {
|
||||||
|
/**
|
||||||
|
* uint16 Platform ID.
|
||||||
|
* <p> 0、Unicode
|
||||||
|
* <p> 1、Macintosh
|
||||||
|
* <p> 2、ISO
|
||||||
|
* <p> 3、Windows
|
||||||
|
* <p> 4、Custom
|
||||||
|
*/
|
||||||
public int platformID;
|
public int platformID;
|
||||||
|
/**
|
||||||
|
* uint16 Platform-specific encoding ID.
|
||||||
|
* <p> platform ID = 3
|
||||||
|
* <p> 0、Symbol
|
||||||
|
* <p> 1、Unicode BMP
|
||||||
|
* <p> 2、ShiftJIS
|
||||||
|
* <p> 3、PRC
|
||||||
|
* <p> 4、Big5
|
||||||
|
* <p> 5、Wansung
|
||||||
|
* <p> 6、Johab
|
||||||
|
* <p> 7、Reserved
|
||||||
|
* <p> 8、Reserved
|
||||||
|
* <p> 9、Reserved
|
||||||
|
* <p> 10、Unicode full repertoire
|
||||||
|
*/
|
||||||
public int encodingID;
|
public int encodingID;
|
||||||
|
/**
|
||||||
|
* uint32 从 cmap 表开头到子表的字节偏移量
|
||||||
|
*/
|
||||||
public int offset;
|
public int offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CmapFormat {
|
private static class CmapFormat {
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> cmapFormat 子表的格式类型
|
||||||
|
*/
|
||||||
public int format;
|
public int format;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 这个 Format 表的长度(以字节为单位)
|
||||||
|
*/
|
||||||
public int length;
|
public int length;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 platformID=1 时有效
|
||||||
|
*/
|
||||||
public int language;
|
public int language;
|
||||||
public byte[] glyphIdArray;
|
/**
|
||||||
}
|
* uint16[256]
|
||||||
|
* <p> 仅 Format=2
|
||||||
private static class CmapFormat4 extends CmapFormat {
|
* <p> 将高字节映射到 subHeaders 的数组:值为 subHeader 索引x8
|
||||||
|
*/
|
||||||
|
public int[] subHeaderKeys;
|
||||||
|
/**
|
||||||
|
* uint16[]
|
||||||
|
* <p> 仅 Format=2
|
||||||
|
* <p> subHeader 子标头的可变长度数组
|
||||||
|
* <p> 其结构为 uint16[][4]{ {uint16,uint16,int16,uint16}, ... }
|
||||||
|
*/
|
||||||
|
public int[] subHeaders;
|
||||||
|
/**
|
||||||
|
* uint16 segCount x2
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> seg段计数乘以 2。这是因为每个段用两个字节表示,所以这个值是实际段数的两倍。
|
||||||
|
*/
|
||||||
public int segCountX2;
|
public int segCountX2;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 小于或等于段数的最大二次幂,再乘以 2。这是为二分查找优化搜索过程。
|
||||||
|
*/
|
||||||
public int searchRange;
|
public int searchRange;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 等于 log2(searchRange/2),这是最大二次幂的对数。
|
||||||
|
*/
|
||||||
public int entrySelector;
|
public int entrySelector;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> segCount * 2 - searchRange 用于调整搜索范围的偏移。
|
||||||
|
*/
|
||||||
public int rangeShift;
|
public int rangeShift;
|
||||||
|
/**
|
||||||
|
* uint16[segCount]
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 每个段的结束字符码,最后一个是 0xFFFF,表示 Unicode 范围的结束。
|
||||||
|
*/
|
||||||
public int[] endCode;
|
public int[] endCode;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 固定设置为 0,用于填充保留位以保持数据对齐。
|
||||||
|
*/
|
||||||
public int reservedPad;
|
public int reservedPad;
|
||||||
|
/**
|
||||||
|
* uint16[segCount]
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 每个段的起始字符码。
|
||||||
|
*/
|
||||||
public int[] startCode;
|
public int[] startCode;
|
||||||
public short[] idDelta;
|
/**
|
||||||
public int[] idRangeOffset;
|
* int16[segCount]
|
||||||
public int[] glyphIdArray;
|
* <p> 仅 Format=4
|
||||||
}
|
* <p> 用于计算字形索引的偏移值。该值被加到从 startCode 到 endCode 的所有字符码上,得到相应的字形索引。
|
||||||
|
*/
|
||||||
private static class CmapFormat6 extends CmapFormat {
|
public int[] idDelta;
|
||||||
|
/**
|
||||||
|
* uint16[segCount]
|
||||||
|
* <p> 仅 Format=4
|
||||||
|
* <p> 偏移到 glyphIdArray 中的起始位置,如果没有额外的字形索引映射,则为 0。
|
||||||
|
*/
|
||||||
|
public int[] idRangeOffsets;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=6
|
||||||
|
* <p> 子范围的第一个字符代码。这是连续字符代码范围的起始点。
|
||||||
|
*/
|
||||||
public int firstCode;
|
public int firstCode;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 仅 Format=6
|
||||||
|
* <p> 子范围中字符代码的数量。这表示从 firstCode 开始,连续多少个字符代码被包含
|
||||||
|
*/
|
||||||
public int entryCount;
|
public int entryCount;
|
||||||
|
/**
|
||||||
|
* 字形索引数组
|
||||||
|
* <p> Format=0 为 bye[256]数组
|
||||||
|
* <p> Format>0 为 uint16[] 数组
|
||||||
|
* <p> Format>12 为 uint32[] 数组
|
||||||
|
* <p> @url <a href="https://learn.microsoft.com/zh-cn/typography/opentype/spec/cmap#language">Microsoft cmap文档</a>
|
||||||
|
*/
|
||||||
public int[] glyphIdArray;
|
public int[] glyphIdArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CmapFormat12 extends CmapFormat {
|
/**
|
||||||
public int reserved;
|
* 字形轮廓数据表
|
||||||
public int length;
|
*/
|
||||||
public int language;
|
|
||||||
public int numGroups;
|
|
||||||
public List<int[]> groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GlyfLayout {
|
private static class GlyfLayout {
|
||||||
public short numberOfContours; // 非负值为简单字型,负值为复合字型
|
/**
|
||||||
|
* int16 非负值为简单字形的轮廓数,负值表示为复合字形
|
||||||
|
*/
|
||||||
|
public short numberOfContours;
|
||||||
|
/**
|
||||||
|
* int16 Minimum x for coordinate data.
|
||||||
|
*/
|
||||||
public short xMin;
|
public short xMin;
|
||||||
|
/**
|
||||||
|
* int16 Minimum y for coordinate data.
|
||||||
|
*/
|
||||||
public short yMin;
|
public short yMin;
|
||||||
|
/**
|
||||||
|
* int16 Maximum x for coordinate data.
|
||||||
|
*/
|
||||||
public short xMax;
|
public short xMax;
|
||||||
|
/**
|
||||||
|
* int16 Maximum y for coordinate data.
|
||||||
|
*/
|
||||||
public short yMax;
|
public short yMax;
|
||||||
public int[] endPtsOfContours; // length=numberOfContours
|
/**
|
||||||
public int instructionLength;
|
* 简单字形数据
|
||||||
public byte[] instructions; // length=instructionLength
|
*/
|
||||||
public byte[] flags;
|
public GlyphTableBySimple glyphSimple;
|
||||||
public short[] xCoordinates; // length = flags.length
|
/**
|
||||||
public short[] yCoordinates; // length = flags.length
|
* 复合字形数据
|
||||||
|
*/
|
||||||
|
public LinkedList<GlyphTableComponent> glyphComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ByteArrayReader {
|
/**
|
||||||
private final byte[] buffer;
|
* 简单字形数据表
|
||||||
public final ByteBuffer byteBuffer;
|
*/
|
||||||
|
private static class GlyphTableBySimple {
|
||||||
|
/**
|
||||||
|
* uint16[numberOfContours]
|
||||||
|
*/
|
||||||
|
int[] endPtsOfContours;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
|
int instructionLength;
|
||||||
|
/**
|
||||||
|
* uint8[instructionLength]
|
||||||
|
*/
|
||||||
|
int[] instructions;
|
||||||
|
/**
|
||||||
|
* uint8[variable]
|
||||||
|
* <p> bit0: 该点位于曲线上
|
||||||
|
* <p> bit1: < 1:xCoordinate为uint8 >
|
||||||
|
* <p> bit2: < 1:yCoordinate为uint8 >
|
||||||
|
* <p> bit3: < 1:下一个uint8为此条目之后插入的附加逻辑标志条目的数量 >
|
||||||
|
* <p> bit4: < bit1=1时表示符号[1.正,0.负]; bit1=0时[1.x坐标重复一次,0.x坐标读为int16] >
|
||||||
|
* <p> bit5: < bit2=1时表示符号[1.正,0.负]; bit2=0时[1.y坐标重复一次,0.y坐标读为int16] >
|
||||||
|
* <p> bit6: 字形描述中的轮廓可能会重叠
|
||||||
|
* <p> bit7: 保留位,无意义
|
||||||
|
*/
|
||||||
|
int[] flags;
|
||||||
|
/**
|
||||||
|
* uint8[] when(flags&0x02==0x02)
|
||||||
|
* int16[] when(flags&0x12==0x00)
|
||||||
|
*/
|
||||||
|
int[] xCoordinates;
|
||||||
|
/**
|
||||||
|
* uint8[] when(flags&0x04==0x02)
|
||||||
|
* int16[] when(flags&0x24==0x00)
|
||||||
|
*/
|
||||||
|
int[] yCoordinates;
|
||||||
|
}
|
||||||
|
|
||||||
public ByteArrayReader(byte[] buffer, int index) {
|
/**
|
||||||
this.buffer = buffer;
|
* 复合字形数据表
|
||||||
|
*/
|
||||||
|
private static class GlyphTableComponent {
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> bit0: < 1:argument是16bit,0:argument是8bit >
|
||||||
|
* <p> bit1: < 1:argument是有符号值,0:argument是无符号值 >
|
||||||
|
* <p> bit3: 该组件有一个缩放比例,否则比例为1.0
|
||||||
|
* <p> bit5: 表示在此字形之后还有字形
|
||||||
|
*/
|
||||||
|
int flags;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
*/
|
||||||
|
int glyphIndex;
|
||||||
|
/**
|
||||||
|
* x-offset
|
||||||
|
* <p> uint8 when flags&0x03==0
|
||||||
|
* <p> int8 when flags&0x03==1
|
||||||
|
* <p> uint16 when flags&0x03==2
|
||||||
|
* <p> int16 when flags&0x03==3
|
||||||
|
*/
|
||||||
|
int argument1;
|
||||||
|
/**
|
||||||
|
* y-offset
|
||||||
|
* <p> uint8 when flags&0x03==0
|
||||||
|
* <p> int8 when flags&0x03==1
|
||||||
|
* <p> uint16 when flags&0x03==2
|
||||||
|
* <p> int16 when flags&0x03==3
|
||||||
|
*/
|
||||||
|
int argument2;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 值类型为 F2DOT14 的组件缩放X比例值
|
||||||
|
*/
|
||||||
|
float xScale;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 值类型为 F2DOT14 的2x2变换矩阵01值
|
||||||
|
*/
|
||||||
|
float scale01;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 值类型为 F2DOT14 的2x2变换矩阵10值
|
||||||
|
*/
|
||||||
|
float scale10;
|
||||||
|
/**
|
||||||
|
* uint16
|
||||||
|
* <p> 值类型为 F2DOT14 的组件缩放Y比例值
|
||||||
|
*/
|
||||||
|
float yScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BufferReader {
|
||||||
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public BufferReader(byte[] buffer, int index) {
|
||||||
this.byteBuffer = ByteBuffer.wrap(buffer);
|
this.byteBuffer = ByteBuffer.wrap(buffer);
|
||||||
this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式
|
this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式
|
||||||
this.byteBuffer.position(index); // 设置起始索引
|
this.byteBuffer.position(index); // 设置起始索引
|
||||||
@ -169,7 +520,6 @@ public class QueryTTF {
|
|||||||
return byteBuffer.position();
|
return byteBuffer.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public long ReadUInt64() {
|
public long ReadUInt64() {
|
||||||
return byteBuffer.getLong();
|
return byteBuffer.getLong();
|
||||||
}
|
}
|
||||||
@ -199,51 +549,53 @@ public class QueryTTF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] ReadByteArray(int len) {
|
public byte[] ReadByteArray(int len) {
|
||||||
if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
assert len >= 0;
|
||||||
byte[] result = new byte[len];
|
byte[] result = new byte[len];
|
||||||
byteBuffer.get(result);
|
byteBuffer.get(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short[] ReadInt16Array(int len) {
|
public int[] ReadUInt8Array(int len) {
|
||||||
if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
assert len >= 0;
|
||||||
var result = new short[len];
|
var result = new int[len];
|
||||||
|
for (int i = 0; i < len; ++i) result[i] = byteBuffer.get() & 0xFF;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] ReadInt16Array(int len) {
|
||||||
|
assert len >= 0;
|
||||||
|
var result = new int[len];
|
||||||
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();
|
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] ReadUInt16Array(int len) {
|
public int[] ReadUInt16Array(int len) {
|
||||||
if (len < 0) throw new IllegalArgumentException("Length must not be negative");
|
assert len >= 0;
|
||||||
var result = new int[len];
|
var result = new int[len];
|
||||||
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;
|
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int[] ReadInt32Array(int len) {
|
||||||
|
assert len >= 0;
|
||||||
|
var result = new int[len];
|
||||||
|
for (int i = 0; i < len; ++i) result[i] = byteBuffer.getInt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Header fileHeader = new Header();
|
private final Header fileHeader = new Header();
|
||||||
private final Map<String, Directory> directorys = new HashMap<>();
|
private final HashMap<String, Directory> directorys = new HashMap<>();
|
||||||
private final NameLayout name = new NameLayout();
|
private final NameLayout name = new NameLayout();
|
||||||
private final HeadLayout head = new HeadLayout();
|
private final HeadLayout head = new HeadLayout();
|
||||||
private final MaxpLayout maxp = new MaxpLayout();
|
private final MaxpLayout maxp = new MaxpLayout();
|
||||||
private final List<Integer> loca = new LinkedList<>();
|
|
||||||
private final CmapLayout Cmap = new CmapLayout();
|
private final CmapLayout Cmap = new CmapLayout();
|
||||||
private final ConcurrentHashMap<Integer, String> glyf = new ConcurrentHashMap<>();
|
private final int[][] pps = new int[][]{{3, 10}, {0, 4}, {3, 1}, {1, 0}, {0, 3}, {0, 1}};
|
||||||
private final int[][] pps = new int[][]{
|
|
||||||
{3, 10},
|
|
||||||
{0, 4},
|
|
||||||
{3, 1},
|
|
||||||
{1, 0},
|
|
||||||
{0, 3},
|
|
||||||
{0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
public final Map<Integer, String> unicodeToGlyph = new HashMap<>();
|
|
||||||
public final Map<String, Integer> glyphToUnicode = new HashMap<>();
|
|
||||||
public final Map<Integer, Integer> unicodeToGlyphIndex = new HashMap<>();
|
|
||||||
|
|
||||||
private void readNameTable(byte[] buffer) {
|
private void readNameTable(byte[] buffer) {
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("name"));
|
var dataTable = directorys.get("name");
|
||||||
var reader = new ByteArrayReader(buffer, dataTable.offset);
|
assert dataTable != null;
|
||||||
|
var reader = new BufferReader(buffer, dataTable.offset);
|
||||||
name.format = reader.ReadUInt16();
|
name.format = reader.ReadUInt16();
|
||||||
name.count = reader.ReadUInt16();
|
name.count = reader.ReadUInt16();
|
||||||
name.stringOffset = reader.ReadUInt16();
|
name.stringOffset = reader.ReadUInt16();
|
||||||
@ -260,8 +612,9 @@ public class QueryTTF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readHeadTable(byte[] buffer) {
|
private void readHeadTable(byte[] buffer) {
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("head"));
|
var dataTable = directorys.get("head");
|
||||||
var reader = new ByteArrayReader(buffer, dataTable.offset);
|
assert dataTable != null;
|
||||||
|
var reader = new BufferReader(buffer, dataTable.offset);
|
||||||
head.majorVersion = reader.ReadUInt16();
|
head.majorVersion = reader.ReadUInt16();
|
||||||
head.minorVersion = reader.ReadUInt16();
|
head.minorVersion = reader.ReadUInt16();
|
||||||
head.fontRevision = reader.ReadUInt32();
|
head.fontRevision = reader.ReadUInt32();
|
||||||
@ -282,9 +635,28 @@ public class QueryTTF {
|
|||||||
head.glyphDataFormat = reader.ReadInt16();
|
head.glyphDataFormat = reader.ReadInt16();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* glyfId到glyphData的索引
|
||||||
|
* <p> 根据定义,索引零指向“丢失的字符”。
|
||||||
|
* <p> loca.length = maxp.numGlyphs + 1;
|
||||||
|
*/
|
||||||
|
private int[] loca;
|
||||||
|
|
||||||
|
private void readLocaTable(byte[] buffer) {
|
||||||
|
var dataTable = directorys.get("loca");
|
||||||
|
assert dataTable != null;
|
||||||
|
var reader = new BufferReader(buffer, dataTable.offset);
|
||||||
|
if (head.indexToLocFormat == 0) {
|
||||||
|
loca = reader.ReadUInt16Array(dataTable.length / 2);
|
||||||
|
} else {
|
||||||
|
loca = reader.ReadInt32Array(dataTable.length / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void readCmapTable(byte[] buffer) {
|
private void readCmapTable(byte[] buffer) {
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("cmap"));
|
var dataTable = directorys.get("cmap");
|
||||||
var reader = new ByteArrayReader(buffer, dataTable.offset);
|
assert dataTable != null;
|
||||||
|
var reader = new BufferReader(buffer, dataTable.offset);
|
||||||
Cmap.version = reader.ReadUInt16();
|
Cmap.version = reader.ReadUInt16();
|
||||||
Cmap.numTables = reader.ReadUInt16();
|
Cmap.numTables = reader.ReadUInt16();
|
||||||
for (int i = 0; i < Cmap.numTables; ++i) {
|
for (int i = 0; i < Cmap.numTables; ++i) {
|
||||||
@ -294,27 +666,30 @@ public class QueryTTF {
|
|||||||
record.offset = reader.ReadUInt32();
|
record.offset = reader.ReadUInt32();
|
||||||
Cmap.records.add(record);
|
Cmap.records.add(record);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < Cmap.numTables; ++i) {
|
|
||||||
int fmtOffset = Cmap.records.get(i).offset;
|
|
||||||
reader.position(dataTable.offset + fmtOffset);
|
|
||||||
int EndIndex = reader.position();
|
|
||||||
|
|
||||||
int format = reader.ReadUInt16();
|
for (var formatTable : Cmap.records) {
|
||||||
|
int fmtOffset = formatTable.offset;
|
||||||
if (Cmap.tables.containsKey(fmtOffset)) continue;
|
if (Cmap.tables.containsKey(fmtOffset)) continue;
|
||||||
if (format == 0) {
|
reader.position(dataTable.offset + fmtOffset);
|
||||||
|
|
||||||
CmapFormat f = new CmapFormat();
|
CmapFormat f = new CmapFormat();
|
||||||
f.format = format;
|
f.format = reader.ReadUInt16();
|
||||||
f.length = reader.ReadUInt16();
|
|
||||||
f.language = reader.ReadUInt16();
|
|
||||||
f.glyphIdArray = reader.ReadByteArray(f.length - 6);
|
|
||||||
Cmap.tables.put(fmtOffset, f);
|
|
||||||
} else if (format == 4) {
|
|
||||||
CmapFormat4 f = new CmapFormat4();
|
|
||||||
f.format = format;
|
|
||||||
f.length = reader.ReadUInt16();
|
f.length = reader.ReadUInt16();
|
||||||
f.language = reader.ReadUInt16();
|
f.language = reader.ReadUInt16();
|
||||||
|
switch (f.format) {
|
||||||
|
case 0: {
|
||||||
|
f.glyphIdArray = reader.ReadUInt8Array(f.length - 6);
|
||||||
|
// 记录 unicode->glyphId 映射表
|
||||||
|
int unicodeInclusive = 0;
|
||||||
|
int unicodeExclusive = f.glyphIdArray.length;
|
||||||
|
for (; unicodeInclusive < unicodeExclusive; unicodeInclusive++) {
|
||||||
|
unicodeToGlyphId.put(unicodeInclusive, f.glyphIdArray[unicodeInclusive]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
f.segCountX2 = reader.ReadUInt16();
|
f.segCountX2 = reader.ReadUInt16();
|
||||||
int segCount = f.segCountX2 >> 1;
|
int segCount = f.segCountX2 / 2;
|
||||||
f.searchRange = reader.ReadUInt16();
|
f.searchRange = reader.ReadUInt16();
|
||||||
f.entrySelector = reader.ReadUInt16();
|
f.entrySelector = reader.ReadUInt16();
|
||||||
f.rangeShift = reader.ReadUInt16();
|
f.rangeShift = reader.ReadUInt16();
|
||||||
@ -322,50 +697,55 @@ public class QueryTTF {
|
|||||||
f.reservedPad = reader.ReadUInt16();
|
f.reservedPad = reader.ReadUInt16();
|
||||||
f.startCode = reader.ReadUInt16Array(segCount);
|
f.startCode = reader.ReadUInt16Array(segCount);
|
||||||
f.idDelta = reader.ReadInt16Array(segCount);
|
f.idDelta = reader.ReadInt16Array(segCount);
|
||||||
f.idRangeOffset = reader.ReadUInt16Array(segCount);
|
f.idRangeOffsets = reader.ReadUInt16Array(segCount);
|
||||||
f.glyphIdArray = reader.ReadUInt16Array((EndIndex + f.length - reader.position()) >> 1);
|
// 一个包含字形索引的数组,其长度是任意的,取决于映射的复杂性和字体中的字符数量。
|
||||||
Cmap.tables.put(fmtOffset, f);
|
int glyphIdArrayLength = (f.length - 16 - (segCount * 8)) / 2;
|
||||||
} else if (format == 6) {
|
f.glyphIdArray = reader.ReadUInt16Array(glyphIdArrayLength);
|
||||||
CmapFormat6 f = new CmapFormat6();
|
|
||||||
f.format = format;
|
// 记录 unicode->glyphId 映射表
|
||||||
f.length = reader.ReadUInt16();
|
for (int segmentIndex = 0; segmentIndex < segCount; segmentIndex++) {
|
||||||
f.language = reader.ReadUInt16();
|
int unicodeInclusive = f.startCode[segmentIndex];
|
||||||
|
int unicodeExclusive = f.endCode[segmentIndex];
|
||||||
|
int idDelta = f.idDelta[segmentIndex];
|
||||||
|
int idRangeOffset = f.idRangeOffsets[segmentIndex];
|
||||||
|
for (int unicode = unicodeInclusive; unicode <= unicodeExclusive; unicode++) {
|
||||||
|
if (idRangeOffset == 0) {
|
||||||
|
unicodeToGlyphId.put(unicode, (unicode + idDelta) & 0xFFFF);
|
||||||
|
} else {
|
||||||
|
int gIndex = (idRangeOffset / 2) + unicode - unicodeInclusive + segmentIndex;
|
||||||
|
unicodeToGlyphId.put(unicode, gIndex < glyphIdArrayLength ? f.glyphIdArray[gIndex] + idDelta : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
f.firstCode = reader.ReadUInt16();
|
f.firstCode = reader.ReadUInt16();
|
||||||
f.entryCount = reader.ReadUInt16();
|
f.entryCount = reader.ReadUInt16();
|
||||||
|
// 范围内字符代码的字形索引值数组。
|
||||||
f.glyphIdArray = reader.ReadUInt16Array(f.entryCount);
|
f.glyphIdArray = reader.ReadUInt16Array(f.entryCount);
|
||||||
Cmap.tables.put(fmtOffset, f);
|
|
||||||
} else if (format == 12) {
|
|
||||||
CmapFormat12 f = new CmapFormat12();
|
|
||||||
f.format = format;
|
|
||||||
f.reserved = reader.ReadUInt16();
|
|
||||||
f.length = reader.ReadUInt32();
|
|
||||||
f.language = reader.ReadUInt32();
|
|
||||||
f.numGroups = reader.ReadUInt32();
|
|
||||||
f.groups = new LinkedList<>();
|
|
||||||
for (int n = 0; n < f.numGroups; ++n) {
|
|
||||||
f.groups.add(new int[]{reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32()});
|
|
||||||
}
|
|
||||||
Cmap.tables.put(fmtOffset, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readLocaTable(byte[] buffer) {
|
// 记录 unicode->glyphId 映射表
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("loca"));
|
int unicodeIndex = f.firstCode;
|
||||||
var reader = new ByteArrayReader(buffer, dataTable.offset);
|
int unicodeCount = f.entryCount;
|
||||||
var locaTableSize = dataTable.length;
|
for (int gIndex = 0; gIndex < unicodeCount; gIndex++) {
|
||||||
if (head.indexToLocFormat == 0) {
|
unicodeToGlyphId.put(unicodeIndex, f.glyphIdArray[gIndex]);
|
||||||
for (long i = 0; i < locaTableSize; i += 2) loca.add(reader.ReadUInt16());
|
unicodeIndex++;
|
||||||
} else {
|
}
|
||||||
for (long i = 0; i < locaTableSize; i += 4) loca.add(reader.ReadUInt32());
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Cmap.tables.put(fmtOffset, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readMaxpTable(byte[] buffer) {
|
private void readMaxpTable(byte[] buffer) {
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("maxp"));
|
var dataTable = directorys.get("maxp");
|
||||||
var reader = new ByteArrayReader(buffer, dataTable.offset);
|
assert dataTable != null;
|
||||||
maxp.majorVersion = reader.ReadUInt16();
|
var reader = new BufferReader(buffer, dataTable.offset);
|
||||||
maxp.minorVersion = reader.ReadUInt16();
|
maxp.version = reader.ReadUInt32();
|
||||||
maxp.numGlyphs = reader.ReadUInt16();
|
maxp.numGlyphs = reader.ReadUInt16();
|
||||||
maxp.maxPoints = reader.ReadUInt16();
|
maxp.maxPoints = reader.ReadUInt16();
|
||||||
maxp.maxContours = reader.ReadUInt16();
|
maxp.maxContours = reader.ReadUInt16();
|
||||||
@ -382,94 +762,163 @@ public class QueryTTF {
|
|||||||
maxp.maxComponentDepth = reader.ReadUInt16();
|
maxp.maxComponentDepth = reader.ReadUInt16();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readGlyfTable(byte[] buffer) {
|
/**
|
||||||
var dataTable = Objects.requireNonNull(directorys.get("glyf"));
|
* 字形轮廓表 数组
|
||||||
int glyfCount = maxp.numGlyphs;
|
*/
|
||||||
|
private GlyfLayout[] glyfArray;
|
||||||
|
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
private void readGlyfTable(byte[] buffer) {
|
||||||
for (int i = 0; i < glyfCount; i++) {
|
var dataTable = directorys.get("glyf");
|
||||||
final int index = i;
|
assert dataTable != null;
|
||||||
final var reader = new ByteArrayReader(buffer, dataTable.offset + loca.get(index));
|
int glyfCount = maxp.numGlyphs;
|
||||||
executor.submit(() -> {
|
glyfArray = new GlyfLayout[glyfCount + 1]; // 创建容器时,多创建一个作为保留区
|
||||||
int glyfNextIndex = index + 1 < glyfCount ? (dataTable.offset + loca.get(index + 1)) : (dataTable.offset + dataTable.length);
|
|
||||||
byte[] glyph;
|
var reader = new BufferReader(buffer, 0);
|
||||||
short numberOfContours = reader.ReadInt16();
|
for (int index = 1; index <= glyfCount; index++) {
|
||||||
if (numberOfContours > 0) {
|
if (loca[index - 1] == loca[index]) continue; // 当前loca与下一个loca相同,表示这个字形不存在
|
||||||
short g_xMin = reader.ReadInt16();
|
int offset = dataTable.offset + loca[index - 1];
|
||||||
short g_yMin = reader.ReadInt16();
|
// 读GlyphHeaders
|
||||||
short g_xMax = reader.ReadInt16();
|
var glyph = new GlyfLayout();
|
||||||
short g_yMax = reader.ReadInt16();
|
reader.position(offset);
|
||||||
int[] endPtsOfContours = reader.ReadUInt16Array(numberOfContours);
|
glyph.numberOfContours = reader.ReadInt16();
|
||||||
int instructionLength = reader.ReadUInt16();
|
glyph.xMin = reader.ReadInt16();
|
||||||
var instructions = reader.ReadByteArray(instructionLength);
|
glyph.yMin = reader.ReadInt16();
|
||||||
int flagLength = endPtsOfContours[endPtsOfContours.length - 1] + 1;
|
glyph.xMax = reader.ReadInt16();
|
||||||
|
glyph.yMax = reader.ReadInt16();
|
||||||
|
|
||||||
|
// 读Glyph轮廓数据
|
||||||
|
if (glyph.numberOfContours > 0) {
|
||||||
|
// 简单轮廓
|
||||||
|
glyph.glyphSimple = new GlyphTableBySimple();
|
||||||
|
glyph.glyphSimple.endPtsOfContours = reader.ReadUInt16Array(glyph.numberOfContours);
|
||||||
|
glyph.glyphSimple.instructionLength = reader.ReadUInt16();
|
||||||
|
glyph.glyphSimple.instructions = reader.ReadUInt8Array(glyph.glyphSimple.instructionLength);
|
||||||
|
int flagLength = glyph.glyphSimple.endPtsOfContours[glyph.glyphSimple.endPtsOfContours.length - 1] + 1;
|
||||||
// 获取轮廓点描述标志
|
// 获取轮廓点描述标志
|
||||||
var flags = new byte[flagLength];
|
glyph.glyphSimple.flags = new int[flagLength];
|
||||||
for (int n = 0; n < flagLength; ++n) {
|
for (int n = 0; n < flagLength; ++n) {
|
||||||
flags[n] = reader.ReadInt8();
|
var glyphSimpleFlag = reader.ReadUInt8();
|
||||||
if ((flags[n] & 0x08) != 0x00) {
|
glyph.glyphSimple.flags[n] = glyphSimpleFlag;
|
||||||
|
if ((glyphSimpleFlag & 0x08) == 0x08) {
|
||||||
for (int m = reader.ReadUInt8(); m > 0; --m) {
|
for (int m = reader.ReadUInt8(); m > 0; --m) {
|
||||||
flags[++n] = flags[n - 1];
|
glyph.glyphSimple.flags[++n] = glyphSimpleFlag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取轮廓点描述x,y相对值
|
|
||||||
ByteBuffer xyCoordinatesBuffer = ByteBuffer.allocate(flagLength * 4);
|
|
||||||
// 获取轮廓点描述x轴相对值
|
// 获取轮廓点描述x轴相对值
|
||||||
|
glyph.glyphSimple.xCoordinates = new int[flagLength];
|
||||||
for (int n = 0; n < flagLength; ++n) {
|
for (int n = 0; n < flagLength; ++n) {
|
||||||
short same = (short) ((flags[n] & 0x10) != 0 ? 1 : -1);
|
switch (glyph.glyphSimple.flags[n] & 0x12) {
|
||||||
if ((flags[n] & 0x02) != 0) {
|
case 0x02:
|
||||||
xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
|
glyph.glyphSimple.xCoordinates[n] = -1 * reader.ReadUInt8();
|
||||||
} else {
|
break;
|
||||||
xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
|
case 0x12:
|
||||||
|
glyph.glyphSimple.xCoordinates[n] = reader.ReadUInt8();
|
||||||
|
break;
|
||||||
|
case 0x10:
|
||||||
|
glyph.glyphSimple.xCoordinates[n] = 0; // 点位数据重复上一次数据,那么相对数据变化量就是0
|
||||||
|
break;
|
||||||
|
case 0x00:
|
||||||
|
glyph.glyphSimple.xCoordinates[n] = reader.ReadInt16();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取轮廓点描述y轴相对值
|
// 获取轮廓点描述y轴相对值
|
||||||
|
glyph.glyphSimple.yCoordinates = new int[flagLength];
|
||||||
for (int n = 0; n < flagLength; ++n) {
|
for (int n = 0; n < flagLength; ++n) {
|
||||||
short same = (short) ((flags[n] & 0x20) != 0 ? 1 : -1);
|
switch (glyph.glyphSimple.flags[n] & 0x24) {
|
||||||
if ((flags[n] & 0x04) != 0) {
|
case 0x04:
|
||||||
xyCoordinatesBuffer.putShort((short) (same * reader.ReadUInt8()));
|
glyph.glyphSimple.yCoordinates[n] = -1 * reader.ReadUInt8();
|
||||||
|
break;
|
||||||
|
case 0x24:
|
||||||
|
glyph.glyphSimple.yCoordinates[n] = reader.ReadUInt8();
|
||||||
|
break;
|
||||||
|
case 0x20:
|
||||||
|
glyph.glyphSimple.yCoordinates[n] = 0; // 点位数据重复上一次数据,那么相对数据变化量就是0
|
||||||
|
break;
|
||||||
|
case 0x00:
|
||||||
|
glyph.glyphSimple.yCoordinates[n] = reader.ReadInt16();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
xyCoordinatesBuffer.putShort(same == 1 ? (short) 0 : reader.ReadInt16());
|
// 复合轮廓
|
||||||
|
glyph.glyphComponent = new LinkedList<>();
|
||||||
|
while (true) {
|
||||||
|
var glyphTableComponent = new GlyphTableComponent();
|
||||||
|
glyphTableComponent.flags = reader.ReadUInt16();
|
||||||
|
glyphTableComponent.glyphIndex = reader.ReadUInt16();
|
||||||
|
switch (glyphTableComponent.flags & 0b11) {
|
||||||
|
case 0b00:
|
||||||
|
glyphTableComponent.argument1 = reader.ReadUInt8();
|
||||||
|
glyphTableComponent.argument2 = reader.ReadUInt8();
|
||||||
|
break;
|
||||||
|
case 0b10:
|
||||||
|
glyphTableComponent.argument1 = reader.ReadInt8();
|
||||||
|
glyphTableComponent.argument2 = reader.ReadInt8();
|
||||||
|
break;
|
||||||
|
case 0b01:
|
||||||
|
glyphTableComponent.argument1 = reader.ReadUInt16();
|
||||||
|
glyphTableComponent.argument2 = reader.ReadUInt16();
|
||||||
|
break;
|
||||||
|
case 0b11:
|
||||||
|
glyphTableComponent.argument1 = reader.ReadInt16();
|
||||||
|
glyphTableComponent.argument2 = reader.ReadInt16();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (glyphTableComponent.flags & 0b11001000) {
|
||||||
|
case 0b00001000:
|
||||||
|
// 有单一比例
|
||||||
|
glyphTableComponent.yScale = glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
break;
|
||||||
|
case 0b01000000:
|
||||||
|
// 有X和Y的独立比例
|
||||||
|
glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
glyphTableComponent.yScale = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
break;
|
||||||
|
case 0b10000000:
|
||||||
|
// 有2x2变换矩阵
|
||||||
|
glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
glyphTableComponent.scale01 = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
glyphTableComponent.scale10 = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
glyphTableComponent.yScale = ((float) reader.ReadUInt16()) / 16384.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
glyph.glyphComponent.add(glyphTableComponent);
|
||||||
|
if ((glyphTableComponent.flags & 0x20) == 0) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 保存轮廓点描述x,y相对值ByteArray
|
glyfArray[index] = glyph; // 根据文档 glyfId=0 作为保留区使用,这里赋值从索引1开始
|
||||||
glyph = xyCoordinatesBuffer.array();
|
|
||||||
} else {
|
|
||||||
// 复合字体未做详细处理
|
|
||||||
glyph = reader.ReadByteArray(glyfNextIndex - (reader.position() - 2));
|
|
||||||
}
|
|
||||||
glyf.put(index, getHexFromBytes(glyph));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
executor.shutdown();
|
|
||||||
try {
|
|
||||||
boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e("queryTTF", "glyf表解析出错: " + e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeHashTable() {
|
/**
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
* 使用轮廓索引值获取轮廓数据
|
||||||
for (int i = 0; i < 130000; ++i) {
|
*
|
||||||
final int key = i;
|
* @param glyfId 轮廓索引
|
||||||
executor.submit(() -> {
|
* @return 轮廓数据
|
||||||
Integer gid = queryGlyfIndex(key);
|
*/
|
||||||
if (gid < glyf.size()) {
|
public String getGlyfById(int glyfId) {
|
||||||
unicodeToGlyphIndex.put(key, gid);
|
var glyph = glyfArray[glyfId];
|
||||||
var val = glyf.get(gid);
|
if (glyph == null) return null; // 过滤不存在的字体轮廓
|
||||||
unicodeToGlyph.put(key, val);
|
String glyphString;
|
||||||
if (!glyphToUnicode.containsKey(val)) glyphToUnicode.put(val, key);
|
if (glyph.numberOfContours >= 0) {
|
||||||
|
// 简单字形
|
||||||
|
int dataCount = glyph.glyphSimple.flags.length;
|
||||||
|
String[] coordinateArray = new String[dataCount];
|
||||||
|
for (int i = 0; i < dataCount; i++) {
|
||||||
|
coordinateArray[i] = glyph.glyphSimple.xCoordinates[i] + "," + glyph.glyphSimple.yCoordinates[i];
|
||||||
}
|
}
|
||||||
});
|
glyphString = String.join("|", coordinateArray);
|
||||||
|
} else {
|
||||||
|
// 复合字形
|
||||||
|
LinkedList<String> glyphIdList = new LinkedList<>();
|
||||||
|
for (var g : glyph.glyphComponent) {
|
||||||
|
glyphIdList.add(String.valueOf(g.glyphIndex));
|
||||||
}
|
}
|
||||||
executor.shutdown();
|
glyphString = "[" + String.join(",", glyphIdList) + "]";
|
||||||
try {
|
|
||||||
boolean b = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e("queryTTF", "建立Unicode&Glyph映射表出错: " + e);
|
|
||||||
}
|
}
|
||||||
|
return glyphString;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -477,162 +926,109 @@ public class QueryTTF {
|
|||||||
*
|
*
|
||||||
* @param buffer 传入TTF字体二进制数组
|
* @param buffer 传入TTF字体二进制数组
|
||||||
*/
|
*/
|
||||||
public QueryTTF(byte[] buffer) {
|
public QueryTTF(final byte[] buffer) {
|
||||||
var fontReader = new ByteArrayReader(buffer, 0);
|
var fontReader = new BufferReader(buffer, 0);
|
||||||
Log.i("queryTTF", "读文件头");
|
// Log.i("QueryTTF", "读文件头"); // 获取文件头
|
||||||
// 获取文件头
|
fileHeader.sfntVersion = fontReader.ReadUInt32();
|
||||||
fileHeader.majorVersion = fontReader.ReadUInt16();
|
fileHeader.numTables = fontReader.ReadUInt16();
|
||||||
fileHeader.minorVersion = fontReader.ReadUInt16();
|
|
||||||
fileHeader.numOfTables = fontReader.ReadUInt16();
|
|
||||||
fileHeader.searchRange = fontReader.ReadUInt16();
|
fileHeader.searchRange = fontReader.ReadUInt16();
|
||||||
fileHeader.entrySelector = fontReader.ReadUInt16();
|
fileHeader.entrySelector = fontReader.ReadUInt16();
|
||||||
fileHeader.rangeShift = fontReader.ReadUInt16();
|
fileHeader.rangeShift = fontReader.ReadUInt16();
|
||||||
// 获取目录
|
// 获取目录
|
||||||
for (int i = 0; i < fileHeader.numOfTables; ++i) {
|
for (int i = 0; i < fileHeader.numTables; ++i) {
|
||||||
Directory d = new Directory();
|
Directory d = new Directory();
|
||||||
d.tag = new String(fontReader.ReadByteArray(4), StandardCharsets.US_ASCII);
|
d.tableTag = new String(fontReader.ReadByteArray(4), StandardCharsets.US_ASCII);
|
||||||
d.checkSum = fontReader.ReadUInt32();
|
d.checkSum = fontReader.ReadUInt32();
|
||||||
d.offset = fontReader.ReadUInt32();
|
d.offset = fontReader.ReadUInt32();
|
||||||
d.length = fontReader.ReadUInt32();
|
d.length = fontReader.ReadUInt32();
|
||||||
directorys.put(d.tag, d);
|
directorys.put(d.tableTag, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("queryTTF", "解析表 name"); // 字体信息,包含版权、名称、作者等...
|
// Log.i("QueryTTF", "解析表 name"); // 字体信息,包含版权、名称、作者等...
|
||||||
readNameTable(buffer);
|
readNameTable(buffer);
|
||||||
Log.i("queryTTF", "解析表 head"); // 获取 head.indexToLocFormat
|
// Log.i("QueryTTF", "解析表 head"); // 获取 head.indexToLocFormat
|
||||||
readHeadTable(buffer);
|
readHeadTable(buffer);
|
||||||
Log.i("queryTTF", "解析表 cmap"); // Unicode编码->轮廓索引 对照表
|
// Log.i("QueryTTF", "解析表 cmap"); // Unicode编码->轮廓索引 对照表
|
||||||
readCmapTable(buffer);
|
readCmapTable(buffer);
|
||||||
Log.i("queryTTF", "解析表 loca"); // 轮廓数据偏移地址表
|
// Log.i("QueryTTF", "解析表 loca"); // 轮廓数据偏移地址表
|
||||||
readLocaTable(buffer);
|
readLocaTable(buffer);
|
||||||
Log.i("queryTTF", "解析表 maxp"); // 获取 maxp.numGlyphs 字体轮廓数量
|
// Log.i("QueryTTF", "解析表 maxp"); // 获取 maxp.numGlyphs 字体轮廓数量
|
||||||
readMaxpTable(buffer);
|
readMaxpTable(buffer);
|
||||||
Log.i("queryTTF", "解析表 glyf"); // 字体轮廓数据表,需要解析loca,maxp表后计算
|
// Log.i("QueryTTF", "解析表 glyf"); // 字体轮廓数据表,需要解析loca,maxp表后计算
|
||||||
readGlyfTable(buffer);
|
readGlyfTable(buffer);
|
||||||
Log.i("queryTTF", "建立Unicode&Glyph映射表");
|
// Log.i("QueryTTF", "建立Unicode&Glyph映射表");
|
||||||
makeHashTable();
|
int glyfArrayLength = glyfArray.length;
|
||||||
Log.i("queryTTF", "字体处理完成");
|
for (var item : unicodeToGlyphId.entrySet()) {
|
||||||
|
int key = item.getKey();
|
||||||
|
int val = item.getValue() + 1; // glyphArray已根据TTF文档将索引0作为保留位,这里从1开始索引
|
||||||
|
if (val >= glyfArrayLength) continue;
|
||||||
|
String glyfString = getGlyfById(val);
|
||||||
|
unicodeToGlyph.put(key, glyfString);
|
||||||
|
glyphToUnicode.put(glyfString, key);
|
||||||
|
}
|
||||||
|
// Log.i("QueryTTF", "字体处理完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
public final HashMap<Integer, String> unicodeToGlyph = new HashMap<>();
|
||||||
// * 获取字体信息 (1=字体名称)
|
public final HashMap<String, Integer> glyphToUnicode = new HashMap<>();
|
||||||
// *
|
public final HashMap<Integer, Integer> unicodeToGlyphId = new HashMap<>();
|
||||||
// * @param nameId 传入十进制字体信息索引
|
|
||||||
// * @return 返回查询结果字符串
|
|
||||||
// */
|
|
||||||
// public String getNameById(int nameId) {
|
|
||||||
// fontReader.index = Objects.requireNonNull(directorys.get("name")).offset;
|
|
||||||
// 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值查找轮廓索引
|
* 使用 Unicode 值获查询廓索引
|
||||||
*
|
*
|
||||||
* @param unicode 传入 Unicode 值
|
* @param unicode 传入 Unicode 值
|
||||||
* @return 返回十进制轮廓索引
|
* @return 轮廓索引
|
||||||
*/
|
*/
|
||||||
public int queryGlyfIndex(int unicode) {
|
public int getGlyfIdByUnicode(int unicode) {
|
||||||
if (unicode == 0) return 0;
|
var result = unicodeToGlyphId.get(unicode);
|
||||||
|
if (result == null) return 0;
|
||||||
int fmtKey = 0;
|
return result + 1; // 根据TTF文档,轮廓索引的定义从1开始
|
||||||
for (var item : pps) {
|
|
||||||
for (CmapRecord record : Cmap.records) {
|
|
||||||
if ((item[0] == record.platformID) && (item[1] == record.encodingID)) {
|
|
||||||
fmtKey = record.offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fmtKey > 0) break;
|
|
||||||
}
|
|
||||||
if (fmtKey == 0) return 0;
|
|
||||||
|
|
||||||
int glyfID = 0;
|
|
||||||
CmapFormat table = Cmap.tables.get(fmtKey);
|
|
||||||
assert table != null;
|
|
||||||
int fmt = table.format;
|
|
||||||
if (fmt == 0) {
|
|
||||||
if (unicode < table.glyphIdArray.length) glyfID = table.glyphIdArray[unicode] & 0xFF;
|
|
||||||
} else if (fmt == 4) {
|
|
||||||
CmapFormat4 tab = (CmapFormat4) table;
|
|
||||||
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] <= unicode) start = middle;
|
|
||||||
else end = middle;
|
|
||||||
}
|
|
||||||
if (tab.endCode[start] < unicode) ++start;
|
|
||||||
if (unicode < tab.startCode[start]) return 0;
|
|
||||||
if (tab.idRangeOffset[start] != 0) {
|
|
||||||
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 = unicode - tab.firstCode;
|
|
||||||
if (0 <= index && index < tab.glyphIdArray.length) glyfID = tab.glyphIdArray[index];
|
|
||||||
} else if (fmt == 12) {
|
|
||||||
CmapFormat12 tab = (CmapFormat12) table;
|
|
||||||
if (unicode > tab.groups.get(tab.numGroups - 1)[1]) return 0;
|
|
||||||
for (int i = 0; i < tab.numGroups; i++) {
|
|
||||||
if (tab.groups.get(i)[0] <= unicode && unicode <= tab.groups.get(i)[1]) {
|
|
||||||
glyfID = tab.groups.get(i)[2] + unicode - tab.groups.get(i)[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return glyfID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用Unicode值获取轮廓索引
|
* 使用 Unicode 值查询轮廓数据
|
||||||
*
|
*
|
||||||
* @param unicode 传入 Unicode 值
|
* @param unicode 传入 Unicode 值
|
||||||
* @return 返回十进制轮廓索引
|
* @return 轮廓数据
|
||||||
*/
|
*/
|
||||||
public int getGlyfIndex(int unicode) {
|
public String getGlyfByUnicode(int unicode) {
|
||||||
var glyfIndex = unicodeToGlyphIndex.get(unicode);
|
return unicodeToGlyph.get(unicode);
|
||||||
if (glyfIndex == null) return 0;
|
|
||||||
return glyfIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用Unicode值获取轮廓数据
|
* 使用轮廓数据反查 Unicode 值
|
||||||
*
|
*
|
||||||
* @param unicode 传入Unicode值
|
* @param glyph 传入轮廓数据
|
||||||
* @return 返回轮廓数组的String值
|
* @return Unicode
|
||||||
*/
|
*/
|
||||||
public String getGlyfByCode(int unicode) {
|
public int getUnicodeByGlyf(String glyph) {
|
||||||
return unicodeToGlyph.getOrDefault(unicode, "");
|
var result = glyphToUnicode.get(glyph);
|
||||||
|
if (result == null) return 0;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用轮廓数据获取Unicode值
|
* Unicode 空白字符判断
|
||||||
*
|
*
|
||||||
* @param glyph 传入轮廓数组的String值
|
* @param unicode 字符的 Unicode 值
|
||||||
* @return 返回Unicode十进制值
|
* @return true:是空白字符; false:非空白字符
|
||||||
*/
|
*/
|
||||||
public int getCodeByGlyf(String glyph) {
|
public boolean isBlankUnicode(int unicode) {
|
||||||
//noinspection ConstantConditions
|
return switch (unicode) {
|
||||||
return glyphToUnicode.getOrDefault(glyph, 0);
|
case 0x0009, // 水平制表符 (Horizontal Tab)
|
||||||
}
|
0x0020, // 空格 (Space)
|
||||||
|
0x00A0, // 不中断空格 (No-Break Space)
|
||||||
/**
|
0x2002, // En空格 (En Space)
|
||||||
* 字体轮廓数据转Hex字符串
|
0x2003, // Em空格 (Em Space)
|
||||||
*
|
0x2007, // 刚性空格 (Figure Space)
|
||||||
* @param glyph 字体轮廓数据
|
0x200A, // 发音修饰字母的连字符 (Hair Space)
|
||||||
* @return 返回轮廓数组的String值
|
0x200B, // 零宽空格 (Zero Width Space)
|
||||||
*/
|
0x200C, // 零宽不连字 (Zero Width Non-Joiner)
|
||||||
public String getHexFromBytes(byte[] glyph) {
|
0x200D, // 零宽连字 (Zero Width Joiner)
|
||||||
if (glyph == null) return "";
|
0x202F, // 狭窄不中断空格 (Narrow No-Break Space)
|
||||||
StringBuilder hexString = new StringBuilder();
|
0x205F // 中等数学空格 (Medium Mathematical Space)
|
||||||
for (byte b : glyph) hexString.append(String.format("%02X", b));
|
-> true;
|
||||||
return hexString.toString();
|
default -> false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(fileTree(dir: 'lib', include: ['rhino-1.7.13-2.jar']))
|
api(fileTree(dir: 'lib', include: ['rhino-1.7.14.jar']))
|
||||||
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
|
BIN
modules/rhino/lib/rhino-1.7.14.jar
Normal file
BIN
modules/rhino/lib/rhino-1.7.14.jar
Normal file
Binary file not shown.
@ -310,7 +310,7 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
|
|||||||
override fun hasFeature(cx: Context, featureIndex: Int): Boolean {
|
override fun hasFeature(cx: Context, featureIndex: Int): Boolean {
|
||||||
@Suppress("UNUSED_EXPRESSION")
|
@Suppress("UNUSED_EXPRESSION")
|
||||||
return when (featureIndex) {
|
return when (featureIndex) {
|
||||||
//Context.FEATURE_ENABLE_JAVA_MAP_ACCESS -> true
|
Context.FEATURE_ENABLE_JAVA_MAP_ACCESS -> true
|
||||||
else -> super.hasFeature(cx, featureIndex)
|
else -> super.hasFeature(cx, featureIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
1
modules/rhino1.7.3/.gitignore
vendored
1
modules/rhino1.7.3/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
Binary file not shown.
@ -49,4 +49,4 @@ rootProject.name = 'legado'
|
|||||||
|
|
||||||
include ':app'
|
include ':app'
|
||||||
include ':modules:book'
|
include ':modules:book'
|
||||||
include ':modules:rhino1.7.3'
|
include ':modules:rhino'
|
Loading…
Reference in New Issue
Block a user