"PDF, or Portable Document Format, is a widely-used file format for sharing and viewing documents. It preserves the layout and formatting of a document across different devices and platforms, making it an ideal choice for business reports, ebooks, and more. PDFs are easy to create, share, and print."
Due to the widespread use of PDF files, the demand for searching PDF document content has increased. Docsearch Plus excels in searching within PDF files. However, there are still some important considerations to keep in mind:
DocSearch Plus - Search File Content for Windows / Android
Search single keyword
Standard search
1. search for take, result documents contain take[xxxx…]. e.g. take, takea, takeb, takec, takeanything.
2. search for info, result documents constain info, infom, infomation, informed, etc.
3. search for "info"(add double quotes), result documents only contain info.
Stemming search
search for take, result documents contain all the relevant words. e.g. take, takes, took, taken, taking. (Note:only work for EngLish)
Boolean Search | |
eye AND ear | documents contain both eye and ear |
eye OR ear | documents contain either eye, or ear, or both |
eye NOT ear | documents contain eye, but not ear |
(eye OR ear) AND nose | documents contain nose, and either eye or ear, or both |
eye ear | by default equivalent to the query [eye OR ear], you can use AND instead by changing it from [menu->Preferences->search ->AND/OR operator] |
Note: AND = & ; Or = | ; NOT = ~ | "eye AND ear" = "eye & ear" "eye OR ear" = "eye | ear" "eye NOT ear" = "eye ~ ear" |
Phrase Search | |
"make up" | the words make and up, in that particular order e.g.
|
Proximity Search | |
"make up"~N | You can find words that are within a specific distance away from each other. To do that, put a tilde ('~') at the end of a phrase, followed by a distance value. For example, to search for documents containing make and up within 5 words of each other, type in: "make up"~5 another example: search for "make up"~3
|
Grep Search (1) | |
/abcd/ | Use the grep search method to search for "abcd". You can only search at the beginning of a word in the indexed data. But the “grep search” is able to search for keywords no matter where they appear in the document… beginning of a word, end of a word, middle of a word, etc. for example: When using "index search": search for “one” in "onetwothree" => success search for “two” in "onetwothree" => fail search for “three” in "onetwothree" => fail When using "grep search": search for “one” in "onetwothree" => success search for “two” in "onetwothree" => success search for “three” in "onetwothree" => success But the "grep" does not use an index so it requires going through the entire document each time. Therefore, it is inefficient in searching large amount of data. |
Grep Search (2) | |
/123.45/ /123\.45/ | "Grep Search" supports regular expression. Some characters have special meanings in regular expression, such as dot (.) asterisk (*) plus (+) etc. For example, in regular expressions, the dot is a special character used to match any one character. Therefore, when searching for "123.45", you have to escape the dot (.) with a backslash (\) and type "123\.45" in the search field. The results are as follows: Type "123.45", you may get the results: "123.45", "123a45", "123b45", "123145", "123x45" ... Type "123\.45", you can accurately find the result you want "123.45" |
前些日子, 為了備份一些重要檔案, 特別找了一些免費的雲端伺服器, 不小心
發現了這一個不錯的備份網站, 嚴格來說它並不是幫你保存文檔的地方, 但
卻可以協助你將一份文件, 轉存到多個雲端服務器上.
它支持相當多的雲端服務, google cloud, dropbox, mediafire, 百度, microsoft one drive,
mega 等.
基本的使用方式是, 在multCloud中, 加入多個雲端服務, 然後將文件上傳到其中一個
服務, 例如先將文件上傳到google, 再利用multCloud的傳輸服務, 複製到 百度, dropbox,
mega 等.. 這樣就可以把文件同時存放在多個地方, 避免其中一個萬一文件不見, 或誤刪
或某個雲端停止服務, 都有多個備份可用.
特點如下:
MultCloud – Transfer Files across Cloud Drives
Directly transfer files from one cloud drive to the other.
File transfer in the background, you can close Browser.
Completely FREE and unlimited data traffic for you.
網址:
如果是上司開車,要坐到副駕駛座表示尊敬與尊重。
如果不是專職司機開車,同事、朋友開車的話,都要坐在副駕駛座上,表示尊敬與尊重。
如果上司開車,乘坐的人有好幾個,就要依情況而定,若車內有和上司同級的人,像是配偶的話,那麼下屬要讓出副駕駛座給他們,坐到後座去。
如果車內只是上司和下屬,那麼唯一的女下屬坐副駕駛座。
當有專職司機開車,乘客只有你和上司的時候,這時下屬還是坐在副駕駛座上,老闆則坐在右後座;如果還有和老闆同級別的人,那麼下屬還是坐副駕駛座,老闆和他同級的人坐在後面
當使用android來提取epub檔的文字時, 本來採用tika的 EpubParser來處理
而EpubParser又是採用android(java) sdk內的
javax.xml.parsers
org.apache.harmony
等類別來處理
但發現有一些缺點:
1.當epub內的xhtml有錯誤時, 會停止工作, 導致html內部分內容無法提取
2.org.apache.harmony使用一些 native code (非java code) 這導致除錯及修改
原始碼相當困難.
後來找到另一個 library 叫 TagSoup
是基於Apache License, Version 2.0的自由軟體
(TagSoup is free and Open Source software. As of version 1.2, it is licensed under the Apache License, Version 2.0)
它會最大可能的提取所有文字, 即使html內有錯誤發生
最近在研究如何將pdfbox轉到android中使用, 其中有一個類別PDFont
它是一個記錄pdf格式檔字型的物件, 它的成員如下:
public abstract class PDFont implements COSObjectable, PDFontLike
{
protected static final Matrix DEFAULT_FONT_MATRIX = new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
// TODO: 2016/7/17 hwjane
private static HashMap
protected final COSDictionary dict;
private final CMap toUnicodeCMap;
private final FontMetrics afmStandard14; // AFM for standard 14 fonts
private PDFontDescriptor fontDescriptor;
private List
private float avgFontWidth;
private float fontWidthOfSpace = -1f;
其中toUnicodeCMap 是一個CMap物件, 其結構如下:
public class CMap
{
private int wmode = 0;
private String cmapName = null;
private String cmapVersion = null;
private int cmapType = -1;
private String registry = null;
private String ordering = null;
private int supplement = 0;
private int minCodeLength = 4;
private int maxCodeLength;
// code lengths
private final List
// Unicode mappings
private final Map
CMap中有一個物件charToUnicode , 是一個HashMap的型態, 用來存放將pdf字型轉換成
unicode的轉換表, 所以toUnicodeCMap(或charToUnicode)是一個佔用ram很大的物件.
而偏偏某些pdf檔toUnicodeCMap會有很多個, 但並不代表charToUnicode 也有很多個
, 因為可能200或300個或1000個toUnicodeCMap只指向三或五個charToUnicode,
但依照原來pdfbox的原始碼
toUnicodeCMap = readCMap(toUnicode);
會由 readCMap傳回CMap的物件型態給toUnicodeCMapreadCMap函數:protected final CMap readCMap(COSBase base) throws IOException
在研究在android 中使用pdfbox, 查看原始碼的時候, 發現一個怪現象,
有一個的map的物件, 是用來cache字型的的地方, 但很奇怪它裡面的
字型資料, 總是只能保存大約三種, 第四種字型讀進來時, 就會清除
其中一種, 導致cahce的字型幾乎沒沒什麼作用, 因為每次取用字型時,
都不能從cache讀取, 而是要從檔案讀取, 後來看了原始碼才發現, 其
中PDFont這個物件被定義成SoftReference了.
參考軟引用與其他幾個引用的說明:
(來源:http://www.cnblogs.com/skywang12345/p/3154474.html)
強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
軟引用(SoftReference)
如果一個對像只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
弱引用(WeakReference)
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
再看看pdfbox的原始碼
說明使用軟引用的原因: 當有記憶體壓力(不足)時, 才回收
/**
* A resource cached based on SoftReference, retains resources until memory pressure causes them
* to be garbage collected.
*/
public class DefaultResourceCache implements ResourceCache
{
//PDFont這個物件被定義成SoftReference了
private final Map
new HashMap
但其所謂記憶體不足時, 才回收SoftReference的物件, 但我實際觀察ram的
變化情形, 例如每個app可使用96M的ram, 但當使用到50M左右,
SoftReference的物件就被回收了, 所以所謂的ram不足, 應不是指整個可用
的ram不足, 也許是一開始給的heap size 不足, 就導致回收發生了, 但據我
粗淺無知的理解, 當heap size不足時, app 會請求更大的 heapsize, 直到超
過app 的最大限制96M才會發出, out of memory.
所以使用SoftReference在我的app, 反而是個造成效率低下的原因了, 取消
SoftReference改成
private final Map
new HashMap
以下是我一個程序中的例子:
cache 是一個map物件, 它會一直用put累積不同的字型資料, 但當字型資料佔用內存
空間達百分之九十時, 必須釋放空間, 否則會因為out of memory 而當掉.
設定檔記憶體低於百分之十時, 自動釋放空間
if (cache != null)
{
cache.put(indirect, font);
Runtime runtime = Runtime.getRuntime();
long max = runtime.maxMemory();
long used = runtime.totalMemory();
if (used>max*.9){
cache.clear();
}
}
如何逐個列出(iterate) map的內容
TreeMap
java.util.Map中是Java集合框架中最重要的接口之一。
Map又稱關聯式陣列(Associative Array),為一種使用key-value pair的方式來存取資料的資料結構。
HashMap 通過它的實現提供類似hash table的數據結構功能, 基本上在使用map時,若無其它考量,則我們應該優先使用HashMap,因其存取資料的時間複雜度可以達到常數時間,非常地快. 另外較特別的是HashMap允許鍵值(key)為null。
TreeMap 實現提供了基於紅黑樹的排序,插入資料時, 會自動根據Key值排序.
https://www.pdf-online.com
http://www.extractpdf.com/
https://www.sejda.com/extract-text-from-pdf
http://www.pdfaid.com/extract-text-pdf.aspx
Cannot load settings from file (C:\Users\User\AndroidStudioProjects\ProjectName\.idea\workspace.xml): Error on line 1: Content is not allowed in prolog. Please correct the file content.
android-studio
把workspace.xml打開來看, 完全是亂碼
處理的方法就是把它刪掉, 讓系統重建
使用時發生明明有的class或關鍵字用ctrl+Shift+F無論如何都找不到
有可能android studio內部的索引資料亂掉了.
使用:
File / Invalidate caches...Restart/Invalidate caches...Restart
再重新開啟android studio, 就會自動重建索引
工作上需要修改apache 的 pdfbox, 所以參考關於pdf的格式說明
以下是我所能找到關於pdf 中文參考最詳盡的資料
原文出自
http://www.programmer-club.com.tw/ShowSameTitleN/general/3856.html
作者 chiuinan2(青衫)
有藍色的部分是我自己加的一些註解及實作心得, 紅色部分為我認為是
重點的地方純粹是自己學習pdf使用, 除了註解外, 所有著作權為
http://www.programmer-club.com.tw/ShowSameTitleN/general/3856.html
作者 chiuinan2(青衫) 所有
[心得] PDF檔案格式 (僅列抽取文字相關部份)
PDF檔是我解過所有的檔案裡最複雜的一個, 裡面牽涉的技術頗多, 因此如果不是非常必要, 我會建議各位採用別人的元件比較方便些.
-------------------------
1. PDF的檔案結構
標準的PDF檔係由下列四個區塊依序所組成:
a. header:內含版本資訊。
b. body:內含實際的文件內容。
c. cross-reference table(簡稱xref table):內含物件參照的相關資訊。
d. trailer:內含指向xref table、body區的重要相關資訊。
各區塊的實際內容,我們後面會再提到,現在先說明一下各區塊內的資料呈現方式。PDF是以行的方式來呈現資料的,每一行的結束字元,可以是Carriage Return(ASCII 13)、也可以是Line Feed(ASCII 10)、或是兩者的組合(ASCII 13緊接著ASCII 10)。
各行內的資料,若遇到%字元,表示該行從%字元開始後面的所有資料都是註解,必須加以略除。但有一個例外,那就是串流物件(stream object)的內容,並非以行的方式呈現,必須另行處理,這點我們後面提到資料物件時,會再加以說明。另外要注意的是,PDF裡的資料是大小寫有關的。
除了標準的PDF檔外,PDF檔是可以加以修改的,而修改的資料,便附加在原PDF檔資料的後面,因此其結構便成為:
header
{body, xref table, trailer}*
各修改的部份,便是利用trailer裡的指標來相互連結,這點在提到trailer內容時再來說明。除了附加修改的PDF檔外,還有一種特殊的PDF檔,稱為線性化PDF檔(Linearized PDF),這是為了適用於網頁傳遞快速顯示所使用的特殊結構,其特點如下:
(1) 整個檔案結構視為修改一次的結果(即有兩個body, xref table, trailer)
(2) 第一個body裡的第一行是註解,裡面包含>=128字碼的字元,body裡其他的資料則是線性化相關的參數
(3) 第二個body才是真正的PDF文件內容
(4) trailer和xref table的連結和正常的PDF檔不太一樣(在說明trailer區塊時再一併說明)
由於線性化PDF檔是以附加修改的PDF檔結構為基準,因此視為後者來處理仍然是正確的,這邊便不再加以區分是否為線性化PDF(只有讀取trailer區塊時要調整一下)。
2. PDF的檔頭
header區塊的資料,其實只有一行註解文字,且固定以”%PDF-“開頭,後面緊接著PDF的版本,例如:
%PDF-1.3
因此可藉由此字串來檢查是否為PDF檔。
3. 開始讀取的地方:trailer區塊
要讀取PDF檔的內容,必須由檔尾的trailer區塊開始讀取,(pdfbox是由trailer開始讀取)經由其內的指標來取得xref table與body內容。trailer區塊的內容如下:
trailer
<<
trailer資料
>>
startxref
xref table開始的檔案位置
%%EOF
trailer資料主要由{/屬性名稱 屬性值}*所組成,以下便是一個例子:
/Size 22
/Root 2 0 R
/Info 1 0 R
/Prev 408
以下則是各屬性的意義(這邊有許多物件結構,暫時不加以說明,大家先以邏輯觀念弄懂整個檔案結構後,再來說明會比較清楚些):
(1) Size:後面接的整數表示整個檔案的xref table的項目數
(2) Prev:如果有的話,後面的整數表示前次內容裡的xref table檔案位置
(3) Root:後面的資料表示文件裡Catalog的物件編號(等要讀取body區塊時再說明)
(4) Info:如果有的話,後面的資料表示文件裡的摘要資訊所在物件編號(等要讀取body區塊時再說明)
(5) ID:如果有的話,後面的陣列資料分別表示舊ID與新ID。這個資料主要做為外部PDF檔連結用,對我們而言並無意義,但其中的第一個ID與加密有關,需要注意
(6) Encrypt:如果有的話,表示PDF檔有加密,其後接的詞典資料,便是用來解密用的(這部份等最後面再來說明)
(如果加密的pdf, 沒有加入Encrypt的模組, 則取出的stream會是錯的)
Bouncy Castle
Java 密碼擴展(JCE)的提供者。
Spongy Castle
Spongy Castle 對最新版本的 BouncyCastle 進行了簡單地重新打包;適用android系統, 所有的 org.bouncycastle.* 包重命名為了 org.spongycastle.*,所有 Java安全 API 提供者的名字由 BC 改為了 SC。
原則上,PDF檔應該要讀取檔尾的最後一個trailer區塊為準,而且只需讀取一個(即使有多個trailer區塊)。但對於線性化PDF檔而言,其應讀取的trailer區塊則是第一個,同時首頁所需的物件參照資訊放在第一個xref table,其餘各頁的物件參照資訊則放到第二個xref table(這樣做的目的,當然是要讓PDF檔尚未傳完時,便能讀取所需的相關資訊,然後當收到body區塊資料時,便能立即顯示第一頁出來)。為了適用於線性化PDF檔,讀取trailer區塊的方式,必須改成:
(1) 由檔尾trailer區塊裡找到startxref,取得xref table開始的檔案位置
(2) 移到該xref table的位置,略過xref table,讀取後面緊接著的trailer區塊內容
由於正常的PDF檔,檔尾trailer區塊startxref指向的xref table都是最後一個xref table,因此略過該xref table資料後,還是會找到檔尾的trailer區塊,並沒有什麼不同。這樣的作法,只是為了同時適用於線性化PDF檔而已。
除了前述整體的文件相關資訊外,trailer區塊另一重要的作用,便是經由其內的指標,讀取所有的xref table資料,為了也同時適用於線性化PDF檔,整個讀取trailer和xref table的過程應為:
(1) 由檔尾trailer區塊裡找到startxref,取得xref table開始的檔案位置
(2) 移到該xref table的位置,略過xref table,讀取後面緊接著的trailer區塊內容
(3) 移到該xref table的位置,開始讀取xref table內容
(4) 找尋其後緊接的trailer區塊中是否有Prev屬性,沒有即結束
(5) 如果有Prev,則其後的數字視為下個xref table的檔案位置,回到步驟3
4. xref table區塊
xref table區塊的格式如下:
xref
物件參照表*
每個物件參照表的格式如下:
物件開始編號 參照表項目數
參照表項目*
每個參照表項目固定20 byte,其格式如下:
(1) byte 0-9:物件資料所在的檔案位置,靠右,不足時補0
(2) byte 10:空白字元
(3) byte 11-15:代數(generation number),靠右,不足時補0
(4) byte 16:空白字元
(5) byte 17:n表示物件使用中,f表示物件未被使用(free)
(6) byte 18-19:空白與換行字元
以下便是一個xref table的例子:
xref
0 3
000000003 65535 f
000000127 00000 n
000000000 00001 f
5 1
000004346 00000 n
這個例子表示物件編號0、2、3、4未被使用,物件編號1、5的資料在127、4346的位置裡。其實xref table區塊的資料,就是用來記錄文件內容裡的各物件資料所在的位置。由於PDF檔是可被修改的,因此文件內容的物件一但有被修改的狀況,xref table裡便會記錄下來,以便參照到該物件時,會去讀取新的物件資料,而不是舊的物件資料。比較要注意的是被刪除的狀況,當物件被刪除時,xref table裡會記錄該物件是未被使用的,同時代數會加一,若之後物件有增加時,便可以使用該物件編號。代數改變的目的,是要有所標記,以避免參用到被刪除前的物件資料(因為當參用物件時,除了物件編號,還必須附上代數)。代數不能>=65535,一但達到這個數字,該物件編號便不能再使用,而必須另行增加一個編號。由於我們只是要讀取文件內容資料而已,這部份其實不必管那麼多,只需對所有xref table區塊裡的內容,由新到舊一個一個讀入即可(同一物件編號需以新的為準,舊的不能蓋掉新的),最後形成一個以物件編號做為索引的物件參照表陣列,供文件內容使用。
這邊必須要注意的是,如果xref table或trailer資料不正確時(包括指標值),有可能相關資料毀損,此時必須嘗試掃描整個檔案,重建出xref table與trailer資料才行(trailer必須有Root屬性)。
5. 物件的形態與參用
為了方便後續的說明,因此這裡先介紹一下PDF檔裡的各種物件形態。首先說明物件的參用形式如下:
物件編號 代數 R
大家可以看一下前述trailer區塊裡的Root與Info屬性值做為例子。在文件內容,甚至是物件內容裡,都可能有物件參用形式。例如一個影像的串流資料物件,當PDF writer在寫入時,並不知道它的長度(指one pass產生而言),因此在長度欄裡便可能參用一個整數物件,等寫完後得知長度時,再加入該參用的整數物件,裡面內含該長度的整數數值。為了方便後續的說明,以下我們便先說明各種物件的形態與資料表示方式。一個物件的基本形式如下:
物件編號 代數 obj
物件資料
endobj
物件的形態便是由物件資料來決定的,每個物件只能有一種物件資料。以下便是各種物件的資料表示方式(包括前述的物件參用形式都算):
(1) 布林值物件
只有兩種資料:true和false,利用這兩個字做為區別
(2) 數字物件
包括整數和實數(含負數),實數必須以小數點的形式出現。因此開頭是數字或負號者,便是數字物件。不過由於物件參用形式是整數+整數+R,因此整數物件尚必須往後看兩個物件,才能得知是否為數字物件,還是物件參用形式。
(3) 字串物件
字串物件有兩種表示法,一般可見字元用括號(),16進制表示法用角號<>表示,例如:
(This is a string)
<4e6f762073686d6f7a206b6120706f702e>4e6f762073686d6f7a206b6120706f702e>
注意可見字元包括CR與LF等。如果一行太長寫不下去,則可以用反斜線(backslash,\)做為續行動作,例如:
(These \
two strings \
are the same.)
等於下面這行:
(These two strings are the same.)
另外,也可使用反斜線做為跳逸字元,跳逸字元如下表所示,若出現沒有在下表中的跳逸字元,將會被忽略不顯示。
跳逸字元 說明
\n 換行(Line feed)
\r 歸位(Carriage return)
\t 定位(Horizontal tab)
\b 倒退(Backspace)
\f 跳頁(Form feed)
\( 左括號(Left parenthesis)
\) 右括號(Right parenthesis)
\\ 反斜線(Backslash)
\ddd 8進位轉換字元(Character code ddd (octal))
其中8進位轉換字元並不一定要3個數字字元,只要遇到非0-7的數字,便視為數字結束(但一般建議是補足3個)。必須注意的是,8進位轉換出來的字元是允許ASCII 0的NULL字元的。另外,在()裡的%並不視為註解起始字元,而是視為字串資料的一部份,同時在()裡若還有成對的(),可不必使用跳逸字元,在處理時要特別注意此一情況。
至於16進制的資料必須是成對的,若出現不成對的情況,則必須自動補0,例如<901fa>,需算成有3個byte的字串,分別為90、1F及A0。字串裡的空白字元需略過。901fa>
當PDF檔有加密時(可由trailer區塊得知),字串物件裡的字串都是加密的,必須解密才行,這點以後再說明。
(4) 名稱物件
名稱物件的起始字元為/,其後緊接著名稱字串(大小寫有關),例如/Title(trailer區塊裡的屬性便是名稱物件)。名稱字串裡的字元必須在ASCII 33~255之間,且不能是%()<>[]{}/#這幾個字元。如果名稱中希望含有其它不合法的字元時(ASCII 0的NULL字元除外),必須採用#加兩個16進位數字的方式來表示字元碼。例如想加入一個空白字元,則可用/Adobe#20Green,實際名稱便是Adobe Green。
(5) 陣列物件
陣列物件的格式如下:
[物件*]
在[]裡的物件資料,可以是任何一種物件,包括物件參用形式,例如:
[0 (string) /Name 1 0 R]
這個陣列裡總共有4個物件資料。
(6) 詞典物件
詞典物件的格式如下:
<<
詞典項目*
>>
各詞典項目的格式如下:
名稱物件 物件資料
前面便是屬性名稱,後面便是它的屬性值,可以是任何一種物件,包括物件參用形式。例如trailer區塊,本身便是一個詞典物件。
(7) 串流物件
串流物件主要記錄一連續的任何資料(例如影像資料),其格式如下:
詞典物件
stream
串流資料
endstream
其中stream之後必須以CR+LF或LF結束,不能單純以CR做結束,在LF之後才是真正的串流資料。
在stream前的詞典物件,主要用來說明該串流資料的特性,以下為各屬性的意義:
a. Length:後面的整數表示串流資料裡的字元數(介於stream和endstream之間)
b. Filter:如果有的話,後面的名稱或名稱陣列,表示串流資料應使用的解壓方法與次序(後述)
c. DecodeParms:如果有的話,後面的資料即前述解壓方法所需的參數
d. F:如果有的話,後面的資料表示串流資料所在的檔案名稱,此時該串流資料應被略過不理
e. FFilter:同Filter,但作用於外部檔案的串流資料
f. FDecodeParams:同DecodeParms,但作用於外部檔案的串流資料
以下是Filter裡可能的解壓法名稱:
(在pdfbox中的實作部分在 class FilterFactory)
名稱 有無參數 說明
ASCIIHexDecode或AHx 無 使用16進制ASCII編碼,適用在二元碼資料
ASCII85Decode或A85 無 使用ASCII base-85編碼,適用在二元碼資料
LZWDecode或LZW 有 使用LZW方式壓縮,適用在二元碼及純文字資料
FlateDecode或Fl 有 使用zlib/deflate方式壓縮,適用在二元碼及純文字資料
RunLengthDecode或RL 無 使用byte-oriented run-length演算法編碼,適用在二元碼及純文字資料
CCITTFaxDecode或CCF 有 使用CCITT編碼,適用在單色圖形資料
DCTDecode或DCT 有 使用DCT編碼,適用在一般圖形資料
關於各解壓法的演算法,我們留到後面再來說明。
(8) 空物件
以null來表示。
說明完各種物件後,下面總結一下各種物件的辨識方法:
(1) 以(開頭:字串物件
(2) 以/開頭:名稱物件
(3) 以<開頭:若後面不接<,便是字串物件(見<<開頭的說明)
(4) 以<<開頭:詞典物件,若之後再接stream便是串流物件
(5) 以負號開頭:後面接數字便是數字物件
(6) 以數字開頭:數字物件,整數物件必須再往後看兩個物件,才能決定是否為物件參用形式
(7) 以f開頭:若是false便是布林物件
(8) 以n開頭:若是null便是空物件
(9) 以t開頭:若是true便是布林物件
(10) 以〔開頭:陣列物件
(11) 其他:不合法的物件
6. stream資料的解壓方法
(1) ASCIIHexDecode
ASCIIHex的編碼方式即是將輸入字元編成2個16進位碼,然後以>做為結束,因此在解壓時,只需每次讀取兩個字元進行解碼還原即可,但在取字元時,要注意需略過空白字元(含換行字元等)。
(2) ASCII85Decode
ASCII85是將輸入資料每次取4個byte,然後轉換成5個byte輸出。假設輸入資料為b1~b4,輸出資料為c1~c5,則其關係為:
b1*256^3 + b2*256^2 + b3*256 + b4 = c1*85^4 + c2*85^3 + c3*85^2 + c4*85 + c5
也就是將256基底改成85基底。但是在輸出時,必須將資料值加上33,使得輸出字元碼在33-117之間。但有一些例外:
(1) 如果5個輸出資料值都是0,則改以一個z字元(ASCII 122)取代
(2) 如果輸入資料不足4 byte時(假設n個),則後面補0計算後,取前n+1個資料值輸出
資料結束係以~>連續兩個字元表示。解壓時同樣要略過空白字元。
(3) LZWDecode
使用標準LZW解壓。它有幾個參數屬性值如下:
a. Predict:如果有的話,後接的整數值>1,表示需要做影像相關的壓縮調整
b. EarlyChange:如果有的話,後接的整數值若為0,表示編碼表需儘量延後擴增(如果是1則會提前一個字碼擴增,內定為1)
LZW的解壓方式很簡單,但在說明解壓步驟之前,需要先說明一下LZW字串表的結構。每個字串表項目主要分成下列三個部份:
Prefix Suffix 字串
字串當然就是字串表記錄的字串(供解壓輸出用),而該字串主要由Prefix和Suffix所結合而成。Suffix一定是一個字元,Prefix可以是一個字元,也可以指向字串表裡的一個項目。由於字串可由Prefix和Suffix所組成,因此也可不必記錄字串,在需要時,再利用項目裡的Prefix和Suffix值組合出來(但速度會較慢)。因為LZW的項目表大小規定最多是4096個,因此Prefix、Suffix這兩個數值使用32 bit的長整數便可以表示。至於字串表的初始資料,由於壓縮資料的字碼範圍為0~255,因此字串表一開始的256個項目便保留做為記錄該字元使用。除此之外,還必須再加兩個特殊字碼:256表示必須重置字串表;257表示資料結束。以下便是LZW的解壓步驟:
a. 重置字串表,將之初始化為258個項目,將CodeLength設為9
b. 由輸入資料中取出CodeLength個位元組成輸入碼(資料不足便有問題)
c. 若之前尚未處理過任何輸入資料(即重置字串表後第一次處理輸入資料),則將Prefix設成輸入碼,並檢查輸入碼,若是256便回到步驟a,若是257便結束,都不是則回到步驟b
d. 將Suffix設成輸入碼,並檢驗Suffix是否為一個字元(<258 br="" refix="" uffix="">e. 將Prefix輸出(注意Prefix可能是一個字元,也可能指向字串表裡的一個項目)
f. 檢查輸入碼,若是256便回到步驟a,若是257便結束
g. 若字串表項數<4096 br="" refix="" uffix="">h. 比對字串表的大小與CodeLength可容納的空間(9=512,10=1024,11=2048,12=4096),如果相等,則將CodeLength加一(注意若是EarlyChange,則CodeLength可容納空間要減一,亦即字串表還剩一個時便擴增字串表),若CodeLength>=12便不再增加(即CodeLength最大只能12)
i. 將Prefix設成輸入碼,回到步驟b4096>258>
<258 br="" refix="" uffix=""><4096 br="" refix="" uffix="">
4096>258>
(4) FlateDecode
由於資料是使用zlib裡的deflate函數壓縮,因此只需用zlib裡的inflate函數解壓即可,和PowerPoint裡的Embeded Object解壓方式相同。但要注意Predict屬性值不能>1(同LZWDecode)。(zip的壓縮法, 最常用)
(5) RunLengthDecode
RunLength壓縮法,是將輸入資料做成下列形式:
長度byte 資料
(1) 長度byte值在0~127時,表示後面接 (長度byte值+1) 個未編碼資料
(2) 長度byte值在129~255時,表示後面接一個資料,必須重複 (257-長度byte值) 次
(3) 長度byte值為128時,表示資料結束
(6) CCITTFaxDecode
這個壓縮法主要用在影像資料上,我們用不著。
(7) DCTDecode
這個壓縮法主要用在影像資料上,我們用不著。
7. 摘要資訊
PDF檔的摘要資訊是放在由trailer區塊裡Info屬性指向的物件,該物件是一個詞典物件,我們需要的只有Author、Title、Subject這幾個屬性值(都是字串資料),其他屬性對我們而言都無意義。不過就我測試結果,PDF檔裡的摘要資訊大多沒有意義,因此也可不抓取。
8. Catalog與Page Tree
由trailer區塊裡Root屬性指向的物件,便是文件內容最上層的物件。該物件為一詞典物件,稱為Catalog物件。以下便是這個詞典物件裡重要的屬性意義如下(對於取出文字內容無用的都不列):
(1) Type:後接名稱物件,必須是Catalog
(2) Pages:指向Page Tree的根節點
所有PDF檔裡各頁的內容,都必須透過Page Tree去取得。Page Tree裡有兩種物件(都是辭典物件),一種是Pages物件,一種是Page物件。Page物件記錄了一頁實際的內容,Pages物件則是用來將Page物件組成Page Tree。以下便是Pages物件的基本屬性意義:
(1) Type:後接名稱物件,必須是Pages
(2) Kids:後接陣列物件,指向所有下層的節點(可能是Pages物件,也可能是Page物件)
(3) Count:後接數字物件,說明本節點開始的子樹共有多少Page物件(即有多少頁)
(4) Parent:指向上層的節點(根節點沒有)
下面則是Page物件的基本屬性意義:
(1) Type:後接名稱物件,必須是Page
(2) Parent:指向上層的節點
(3) Contents:指向文件的內容物件(必須是串流物件,或是由串流物件組成的陣列物件),如果沒有表示空頁
(4) Annots:後接陣列物件,說明本頁的文字註解、影片、聲音等等非顯示/列印用的相關資訊(目前暫不抓取)
除了上述的基本屬性外,下面還有一些是Pages物件與Page物件可共有的重要屬性,當省略時,一律繼承父節點(若一直到根節點都沒有,便用內定值):
(1) Resources:後接詞典物件,說明各頁顯示時所需的額外資源
Resources詞典裡重要的屬性為:
(1) Font:如果有的話,後接的詞典物件為各個字型名稱及其資訊
9. 頁描述命令
PDF檔內各頁的內容,都是記錄在Page物件裡Content屬性值指向的串流物件。然而是否解出這些串流物件資料,就能簡單地抽出文字呢?答案是否定的。要知道PDF檔的目的在於文件的可攜性,其重點在於原文重現,因此PDF各頁的資料,幾乎都是在描述版面上各種點、線、字、圖的顯現方式,並不管文字的順序與內碼語系,這和一般文件編輯程式的存檔有著極大的差異。因此想要抽出PDF檔各頁的文字資料,首先必須了解描述這些版面圖文的語言,也就是頁描述命令。但是在說明這些頁描述命令前,有些排版觀念需要先讓大家了解一下:
(1) PDF writer主要是接收排版軟體或其他類似軟體的列印輸出結果,因此在PDF裡每頁內容的記錄方式均取決於這些軟體的列印輸出方式。有些軟體可能輸出一串文字,有些則是一個字母一個字母輸出,有些則是遇到空白字元就不輸出,情況都不太一樣(像首字大寫時,便可能一個單字分兩次輸出)。因此我們無法從輸出的文字部份判斷是否為一個完整的單字,唯一的方法便是從文字串的列印位置與寬高範圍來加以判斷。
(2) 排版中有許多特殊效果,例如陰影,便是藉由字的重疊列印來產生的,在這種情況下,如果單純去取得文字,會變成一堆重複的字串,即使人眼看起來只有一個字串。要處理這種情況,仍然需要得到文字串的列印位置與寬高範圍,才能加以判斷。
(3) 通常列印的結果,都會有書眉、頁碼等附加在各頁的額外列印資料,有可能各頁都有,或是奇偶頁各自不同,更有可能首頁沒有、其餘各頁有,或是章節變動時又變更書眉與頁碼內容。再加上書眉、頁碼出現的位置,隨著設定的不同,可能出現在頁面上下左右任何一個位置,如果單純要從列印位置去切割這些資料出來,會是相當困難的一件事。然而我們可以從排版軟體列印輸出的習慣上來著手。由於內頁資料是一整體的,排版軟體幾乎都是一次連續輸出完畢,至於書眉、頁碼等附加資料,通常會在一頁最開始或最後面才附加列印上去(除非有其他註記資料)。因此我們可以直接取得每頁文字內容後,再依照上述排版業界對書眉、頁碼的列印習慣,去比對各頁最前面或最後面的字串,便能加以區分出來(至於版權頁便很難區分)。
(4) 要判斷連續的文字串資料是否同屬一個段落、連接時是否要加空白等等,首先要知道各行文字的排印方式。所有排版軟體對於文字的排印,必定是延著基準線往某個方向串接依序排印。如果將之想成座標軸的話,一般都是延著x軸正向列印,特別是列印英文資料的情況下,但也可能反向列印(例如中文便有由右到左與由左到右兩種列印習慣)。至於這個座標軸有可能旋轉任何角度,也就是直排/橫排、紙張直擺/橫擺的區分(也可能是要製作特殊效果的DM稿)。座標軸也可能變形,例如圓弧散狀的文字,便是將圓弧視為x軸,延著線排印文字。但無論如何變形,基本的排版計算方式都大同小異,這點對於要判斷文字接合上是很重要的。不過變形的座標軸,由字的座標是很難推算出來變形的狀況,在這種情況下,便很容易有誤判的情況產生,但目前也沒有什麼好方法可以解決,唯一的解法,大概只有用詞典的方式來判斷了。
綜合上述,我們目前的首要工作,便是取得各文字的列印位置與寬高範圍,因此後面的頁描述命令,我們將特別著重於這部份,其餘命令則可以略過不理。但在提到這些命令前,我們還要說明一下PDF檔裡使用的空間座標關係。PDF檔裡的空間座標一共有下列7種:
(1) 列印設備空間座標(Device Space):也就是實際要輸出的螢幕、印表機的空間座標。
(2) 使用者空間座標(User Space):為PDF檔內運算的基準空間座標,輸出時再轉為列印設備空間座標。
(3) 文字空間座標(Text Space):文字列印時運算的空間座標,必須再轉為使用者空間座標。
(4) 影像空間座標(Image Space):影像列印時運算的空間座標,必須再轉為使用者空間座標。
(5) XObject空間座標(Form Space):XObject資料列印時運算的空間座標,必須再轉為使用者空間座標(XObject是一個串流物件,內含Postscript命令)。
(6) 色彩空間座標(Pattern Space):定義Rendering、Shading等特殊效果時的空間座標,必須再轉為使用者空間座標。
(7) 字型空間座標(Character Space):字型內各文字屬性定義使用的空間座標,在列印文字時需轉為文字空間座標。
由於空間座標眾多,在頁描述命令裡便有許多的命令在定義轉換時使用的運算矩陣。如果我們要實際模擬出PDF各頁的顯示,當然需要去了解這些空間座標的定義與轉換,但如果只是要單純抽取出文字,事實上並不需要這麼麻煩,只需了解文字空間座標轉換成使用者空間座標即可。文字空間座標轉換矩陣由頁描述命令裡的Tm命令所設定,共有6個實數參數,假設為a b c d e f,則在文字空間座標上的一點(x,y),轉換後在使用者空間座標的位置為:
x’ = ax + cy + e
y’ = bx + dy + f
至於寬高則為:
width’ = a*width + c*height
height’ = b*width + d*height
現在我們開始說明PDF檔的頁描述命令,這些命令都是採用倒裝句的語法進行,也就是:
參數1 參數2 … 命令
當遇到命令時,再將前面收集到的參數送入執行。以下為各頁描述命令的意義與參數,需要處理的部份以粗體顯示,其餘部份不必了解太多(必須要有排版與繪圖學相關的知識才看得懂):
pdfbox中處理每個指令的定義在
PDFTextStreamEngine中的addOperator(new DrawObject());
如果要修改別人己寫好的原始代碼, 如果是簡單的修改
某一個函數, 最簡單的方式, 應該就是用繼承的方式, 直
接覆寫(override)其中一個函數. 但是override的方式, 有
時會受到限制, 例如如果該class 被原作者定義為 final,
那麼它所含的函數是不允許覆寫的.
還有覆寫的內容, 如果有用到被定義為private的部分, 也是
不允許的.
遇到以上情況, 就不能用覆寫的方式來處理.
我的處理方法:
把 原來的A class , 複製一份, 變成 B class, 直接更改B class
, 但是注意B class的 package 名稱, 必須和A class 一樣,也就是
如果 A class package 名稱為 com.test.myfile 那麼你的project
內, 要建立一個 com/test/myfile 的路徑, 將 class 放在底下,
否則可能會出現
Classes cannot be accessed from outside package
的問題.
然後將所有對A class 的作業, 都更換為B class
另一個方法是直接去改A class , 再把它 compile 成A.class 檔
直接放到 jar 去置換原來的class. (重新打包). 可參考:
因為最近必須對lucene 的特殊字元進行搜尋(lucene 分詞時
預設是排除特殊字元的, 除了WhiteSpaceAnalyzer), 所以研
究了lucene 分詞的原始碼.
記錄如下:
先說說StandardAnalyzer
StandardAnalyzer extends 自 StopwordAnalyzerBase
當 writer.add(doc) 之後, 會先進入 Analyzer
(所有Analyzer的父類)
然後再根據所選擇的 Analyzer 進入該 Analyzer 執行
CreateComponents 這個函數
CreateComponents 在每一個子類的Analyzer都有不同的實
現方式
例如, 其中StandardAnalyzer 的 CreateComponents 代碼
如下:
@Override
protected TokenStreamComponents createComponents
(final String fieldName, final Reader reader) {
final StandardTokenizer src =
new StandardTokenizer(matchVersion, reader);
src.setMaxTokenLength(maxTokenLength);
TokenStream tok = new StandardFilter(matchVersion, src);
tok = new LowerCaseFilter(matchVersion, tok);
tok = new StopFilter(matchVersion, tok, stopwords);
return new TokenStreamComponents(src, tok) {
@Override
protected void setReader(final Reader reader)
throws IOException {
src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);
super.setReader(reader);
}
};
}
由以上代碼可看出StandardAnalyzer 進行 StandardTokenizer
/StandardFilter/LowerCaseFilter/StopFilter 等程序
再看看WhiteSpaceAnalyzer
@Override
protected TokenStreamComponents createComponents
(final String fieldName,
final Reader reader) {
return new TokenStreamComponents(new
WhitespaceTokenizer(matchVersion, reader));
}
以上可看出 WhiteSpaceAnalyzer, 它的 CreateComponents,
就只有進行WhitespaceTokenizer而己.
以上的StandardTokenizer是進行分詞的程序, 看看它做些什麼
StandardTokenizer中有一個init的函數, 執行
StandardTokenizerImpl
StandardTokenizerImpl 會實際對全文進行分詞. 這個函數相
當複雜, 很難理解, 我只理解一小部分
其中
private static final char [] ZZ_CMAP =
zzUnpackCMap(ZZ_CMAP_PACKED);
ZZ_CMAP 存放每一個字元一個值, 用來判斷該字元是分詞時要納入的
正常值,或是不納入的分割字元.
例如 35是#的ascii內碼
當 ZZ_CMAP[35]=0 時, 代表#在分詞時會被排除"#abc #def ghi" 分詞後=>abc, def, ghi
當 ZZ_CMAP[35]='~' (十進位為126, 為何是126?不解) 時,
代表#在分詞時會被納入"#abc #def ghi" 分詞後=>#abc, #def, ghi
所以想讓#被含在分詞中, 只要 ZZ_CMAP[35]='~' 即可
按下 todo list 視窗右方的filter圖示/Edit filters
上方pattern的地方按+
輸入\btodo: 2016\b.* 代表過濾 開頭為todo: 2016
的所有文字. \b 是固定需要的.
在下方 filter 的地方輸入一個任意名稱, 再勾選剛才
輸入的pattern , 按 ok 退出
現在按下 todo list 視窗右方的filter圖示/選擇剛才輸
入的名稱, 就可以過濾 todo list的內容.
使用 RelativeLayout , 由右向左橫向排列, 但要讓最左邊的
textview 文字可以靠左的方法
就是不要以下樣式:
| 文字文字文字文字 button1 button2|
而是要這樣:
|文字文字文字文字 button1 button2|
關鍵是利用一個 FrameLayout 套在 textview的外面即可
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/btnAdd">
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:text="@string/strSupport"
android:textSize="14sp">
StandardCharsets.UTF_8 是API 19 以上的 class
在 api 19 以下要改成 Charset.forName("UTF-8")
API 19 (含)以上
byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
API 19 以下
byte[] postData = urlParameters.getBytes(Charset.forName("UTF-8"));
Throwable、Error、Exception、RuntimeException 区别 联系
參考上文
實際的經驗:
我有一個 pipeline thread , 就是把一個接一個的 task 丟到 pipeline
裡去執行, 它是一個 queue , 是 fifo 模式, 為的是避免兩個 thread
同時執行, 會有獲取資源衝突的問題, 但當其中某一個 task 出錯
的時候, 也會造成整個 pipeline thread 死掉, 因而後面的 task 也跟
著停止.
今天在解決一個api 16 (android 4.1)誤用 api 19 的
class (StandardCharsets.UTF_8 )時, 突然發現這個錯誤可以用
Throwable 來捕捉, 但用 exception 卻沒辦法.
使用 exception 會因無法捕捉到錯誤, 使整個 pipeline thread 中止,
而用 throwable 來捕捉, 則pipeline thread可繼續存在, 而不影響後
續 task 的執行.
這個誤用api的錯誤, 可能是 error 級的錯誤, 只用 exception 是無法
捕捉的.
所以以後再遇到無法用 exception 捕捉的錯誤, 可試著用 throwable
來試看看.
try {
something...
} catch (Throwable t) { Log.e(TAG, "Error in Pipeline Thread", t); }
在 try catch 結構中, 並非所有的 exception 都能捕捉,
所以如果無論有無 catch 到錯誤, 都必須執行的程序,
可以將它放在 finally 裡面, 這樣就不用擔心, 因 catch 沒
有捕獲錯誤, 而漏掉一定要執行的部分.
例如:
try {
writer.open; something.... } catch (IOException e) { e.printStackTrace(); }finally { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } }
將writer.close() 置於 finally 可確保有無錯誤發生,
也無論有無 catch 到錯誤, 都能保證 writer 能被 close.
執行時, 當停駐在中斷點時, 因背景顏色太深,常
常看不清楚文字, 所以進行更改.
File/Settings/Editor/Color&Fonts/Debugger
先 save as 另存一個樣式名稱
選擇 Execution Point 進行變更
如果不是執行期, 要改變 breakpoint line 的樣式
直接選擇 breakpoint line 進行變更
inlined values for breakpoint line 可以更改執行時
每行用到的變數值的樣式
如果變更之後, 又想更改, 必須先刪整個樣式,
再重新執行上面整個步驟, , 不能直接變更,
不知是不是bug
我的app用到tika的一個小功能, 但是把整個tika的jar大約佔了40MB
, 原來我的app只有3MB, 但把tika加入以後, 即使使用 proguard 減少體
積, 但居然還是變成了10MB, 而且整個編譯執行又慢又卡.
最後實在是受不了了, 於是想把tika重新打包, 只抽出我所需要的
class就好.
程序如下:
1. 先做出兩個目錄
d:\sourcejar : 存放所有tika.jar的解壓class
d:\objectjar : 將需要的class複製到此
2.將 objectjar 內的 class 打包
3.用 jarjar 把 tika 內, 用到 javax.xml.stream 的部分改名
(為了打包後, 包含poi , 而poi 用到javax.xml.stream android 不支援)
4. 將第3步驟的 jar 檔, 複製到 android studio 的 project 中的 libs 目錄內
以上第二及第三步驟沒什麼問題, 但第一步驟如何判斷那些 class 是需要
的呢? 我不知道有沒有更好的方法, 但我是使用較笨的方法, 一次一次的
執行, 然後查看錯誤訊息, 看缺那些 class , 就把那些 class 放進來.
其實我一開始的時候, 是編譯 release 版本, 使用 proguard 來看缺少那些
class, 但 proguard 所顯示的 class 實在太多了, 如果照著加進來, 可能也會
佔太多空間.
本來有成功打包成精簡版的 tika , 後來又加入 poi 後, 因為牽扯的 class 太
複雜了, 所以最後還是放棄加入 poi 的想法.
打包用的 dos batch file bat 如下
echo -------------------------------
rem dos batch file %1 path name %2 file name
rem 目的目錄如果己存在的 class 則結束
if exist "d:\objectjar\%1\%2.class" goto A
md "d:\objectjar\%1"
d:
cd \objectjar
del *.jar
copy "d:\sourcejar\%1\%2.class" "d:\objectjar\%1"
rem 打包
"C:\Program Files\Java\jdk1.8.0_91\bin\jar" -cf d:\objectjar\neat-tika-1.12.jar *
rem ** according to rules.txt to mend content of class, then output to libs
rem 依 rules.txt 的內容修正class名稱
java -jar d:\jar\jarjar-1.3.jar process d:\jar\rules.txt D:\objectjar\neat-tika-1.12.jar D:\Android\StudioProjects\MyApplication\app\libs\neat-tika-1.12.jar
goto done
:A
echo File %2 Exists!
:done
Android Studio 不允許對 root 的名稱重新命名