MyScript 開發文件

SharpCJ發表於2021-11-14

一、IInk SDK runtime

1.1 引擎建立

iink SDK 執行時由一個Engine物件表示。

此物件將允許您建立其他關鍵物件、配置識別和微調 SDK 行為。

它通過類的create()靜態方法例項化Engine:

在 java 中,我們強烈建議將引擎建立包裝成單例

import com.myscript.certificate.MyCertificate;
import com.myscript.iink.Engine;

public class IInkApplication extends Application
{
  private static Engine engine;

  public static synchronized Engine getEngine()
  {
    if (engine == null)
    {
      engine = Engine.create(MyCertificate.getBytes());
    }

    return engine;
  }
}

1.2 物件釋放

在關閉應用程式或開啟新包之前,請確保首先釋放對相應物件的所有引用。如果不這樣做,您可能會遇到意外行為,因為仍會分配本機資源。

此資源管理必須應用於實現該IAutoCloseable介面的所有物件:ContentPackage、ContentPart、 Editor、Engine、ParameterSet、 RecognitionAssetsBuilder和Renderer。

要強制立即釋放本機資源,您必須顯式呼叫close相應物件的方法。通過將它們的引用也設定為 null,確保不要在其他地方引用它們。

// close contentPart and set its reference to null
if (contentPart != null)
{
  contentPart.close();
  contentPart = null;
}

// then close contentPackage and set its reference to null
if (contentPackage != null)
{
  contentPackage.close();
  contentPackage = null;
}

1.3 獲取並設定配置

配置

必須配置一個新引擎,讓 iink SDK 知道要識別哪種內容。

作為一個靈活的工具包,iink SDK 還附帶了許多配置引數。雖然預設值在大多數情況下都有意義,但有時可能需要對其進行配置以滿足您的需要。

訪問配置

可以通過呼叫 getConfiguration() 引擎物件來獲取配置。

可以將實現 IConfigurationListener 介面的偵聽器附加到給定的配置物件,以便在進行配置更改時得到通知。

配置識別

要識別任何語言的數學、圖表或文字,需要建立引擎並指向所需的資產。

資產包由一組配置檔案( *.conf)組成,這些 檔案指定所需的識別引數,這些引數組合到稱為資原始檔( *.res) 的二進位制檔案中。資原始檔包含引擎識別所需的所有內容。

示例一,配置中文:
將中文語言配置檔案下載下來,防止到對應資料夾:

import com.myscript.iink.Configuration;
import com.myscript.iink.Engine;
import java.io.File;

...
Engine engine = IInkApplication.getEngine();

// configure recognition
Configuration conf = engine.getConfiguration();
conf.setStringArray("configuration-manager.search-path", new String[] { "zip://" + getPackageCodePath() + "!/assets/conf" });
conf.setString("lang", "zh_CN");

配置數學計算結果浮點精度為2位(預設位3):

import com.myscript.iink.Configuration;
import com.myscript.iink.Engine;
import java.io.File;

public class Calculator
{

  private Engine engine;

  /**
   * @param packageCodePath when used from an AppCompatActivity object, getPackageCodePath() output.
   * @param filesDirPath when used from an AppCompatActivity object, getFilesDir().getPath() output.
   */
  public Calculator(String packageCodePath, String filesDirPath)
  {

    //Creating engine and accessing the configuration object
    engine = IInkApplication.getEngine();
    Configuration configuration = engine.getConfiguration();

    // set the recognition configuration
    configuration.setStringArray("configuration-manager.search-path", new String[]{"zip://" + packageCodePath + "!/assets/conf"});

    // set the temporary directory
    String tempDir = filesDirPath + File.separator + "tmp";
    configuration.setString("content-package.temp-folder", tempDir);

    // set the math fractional part precision
    configuration.setNumber("math.solver.fractional-part-digits", 2);
  }
}

二、檔案儲存

2.1 支援的內容的型別

Interactive Ink SDK 目前支援以下內容型別:

  • 文字( "Text") - 簡單的多行文字塊(包括可能的換行符),響應式迴流。它預設顯示參考線,但可以停用這些參考線(例如,當處理來自外部來源的未在參考線上對齊的墨水時)。

  • Math ( "Math") - 支援一個或多個方程的塊

  • 圖表( "Diagram") - 支援圖表的塊,具有自動文字/非文字區分和動態重組的可能性。

  • 繪圖( "Drawing") - 塊託管一組筆畫,無需任何解釋。適用於塗鴉和繪畫活動。

  • 文字文件( "Text Document") - 託管文字、數學、圖表、原始內容和繪圖塊的有序集合的容器。它是一個垂直、動態和響應式的內容流。

  • 原始內容( "Raw Content") - 阻止託管原始數字墨水,沒有明確分割為文字、數學、圖表或 iink SDK 已知語義的其他專案。iink SDK 分析內容以從其餘內容中檢索與文字塊對應的墨跡。在原始數字墨水上實現墨水搜尋功能是關鍵。您可以通過 JIIX 匯出獲取此資訊。在非文字塊中,根據其配置,iink SDK 可以進一步區分形狀與其他塊。通過自定義樣式,您可以在編寫此分類時立即獲得反饋。

括號之間提供的字串值區分大小寫,並以明確的方式標識模型中的型別。

2.2 模型結構

ContentPackage
ContentPackage 可以理解為一個儲存墨水的容器,是 Part 的有序集合。它可以儲存為檔案系統上的一個檔案,然後在使用者之間共享或者重新載入。

ContentPart
ContentPart 對應於一個獨立的內容單元,可以唄 iink SDK 處理。每個 part 都有一個特定的型別。對應於其根塊的型別,可以通過其 getType() 方法檢索。

可以通過呼叫 getPartCount() 來獲取單個 package 的 part 數量,getPart()並將其傳遞給要載入的部件的基於 0 的索引。

ContentBlock
ContentBlock 稱之為一個內容塊,通常對應於一個可操作的語義單元。

儘管一個 part 有時承載單個 ContentBlock ,但這兩個概念並不等效。一個 ContentPart 對應一個序列化單元,而一個 ContentBlock 對應一個可操作的語義單元。

2.3 ContentPackage 的相關操作

2.3.1 臨時資料夾

MyScript iink SDK 也需要對檔案系統上的至少一個資料夾具有讀/寫訪問許可權:臨時資料夾,它將輸出正在處理的中間檔案。預設情況下,iink SDK 使用包檔案所在的資料夾。某些平臺強制要求設定臨時資料夾,可以通過設定 content-package.temp-folder 來更改臨時資料夾。

2.3.2 建立和載入 ContentPackage

呼叫engine.openPackage()介面,有如下過載介面

ContentPackage openPackage(java.io.File file)
Opens the specified package using the EXISTING package open option.

ContentPackage openPackage(java.io.File file, PackageOpenOption openOption)
Opens the specified package.

ContentPackage openPackage(java.lang.String path)
Opens the specified package using the EXISTING package open option.

ContentPackage openPackage(java.lang.String path, PackageOpenOption openOption)
Opens the specified package.

其中 PackageOpenOption 有如下選項:

  • EXISTING 開啟現有包並在它不存在時失敗(預設行為)。
  • CREATE 開啟現有包或建立它,如果它不存在。
  • CREATE_NEW 確保建立和開啟以前不存在的包。
  • TRUNCATE_EXISTING 建立並開啟一個新包,覆蓋同一位置的任何預先存在的包。

要建立新的包,也可以呼叫 engine.createPackage(),等同於 engine.openPackage() 使用 CREATE_NEW 選項。

2.3.3 儲存 ContentPackage

Interactive Ink SDK 提供了兩種不同的方法來儲存內容: save()saveToTemp() 兩個方法都是需要在 ContentPackage 例項上呼叫。

  • save()將所有資料序列化到以包命名的 zip 存檔中,無論這些資料是在記憶體中還是已解除安裝到臨時資料夾中。生成的檔案是獨立的,可以在以後重新載入。由於壓縮,這種方法相當慢。
  • saveToTemp()將載入到記憶體中的內容儲存到臨時資料夾中。由於它只寫入給定時間點記憶體中的內容並且不需要壓縮,因此呼叫此方法要快得多。如果 iink SDK 沒有儲存到壓縮存檔而被迫退出,它可以讓 iink SDK 恢復此類資料。

2.3.4 刪除 ContentPackage

呼叫 deletePackage() 刪除 ContentPackage,刪除之前需要確保釋放對這個包的所有引用,包括你通過顯示呼叫它們各自的 close() 方法開啟的部分。

2.4 ContentPart 的相關操作

2.4.1 建立 Contentpart

ContentPart contentPackage.createPart(java.lang.String type)
建立根塊,需要傳入內容的型別。

contentPackage.clonePart(ContentPart part)
克隆一個已經存在的 ContentPart 新增到當前的 ContentPackage。

要將部件從一個包“移動”到另一個包,首先將其克隆到新包中,然後從原始包中刪除該部件。

2.4.2 獲取 ContentPart

contentPackage.getPart(int index)
contentPackage.getPart(String id)
傳入Part 的索引或者 id

2.4.3 刪除 ContentPart

void contentPackage.removePart(ContentPart part)
刪除一個 ContentPart

2.5 後設資料 Metadata

您可以將後設資料附加到ContentPart和ContentPackage物件,它們將與檔案一起序列化。這可以證明對於儲存客戶端特定的引數很有用。

使用setMetadata()和getMetadata()分別儲存和檢索附加到物件的後設資料。

// Retrieve the metadata from a part
ParameterSet metadata = contentPart.getMetadata();

// Set and Get (key, value) pairs
// ...

// Store the metadata back into the part. You have to set expressively as the content part is a native object and metada is not.
contentPart.setMetadata(metadata);

表示後設資料的結構與引擎配置引數結構相同,操作方式也相同。

三、渲染

Iink SDK 渲染的幾個重要概念:

3.1 渲染目標 Render target

一個實現了 IRenderTarget 或者 IRenderTarget2 的 View, 繪製的內容將顯示在上面。

3.2 畫布 Canvas

畫布物件提供了一個平臺,實現由iink SDK呼叫,以呈現內容繪製命令。它在 ICanvasICanvas2 介面中定義

3.3 渲染器

渲染器負責決定如何呈現每層的內容,決定哪些區需要重新整理,以及模型的面積、引數,如縮放因子或者檢視的偏移量,它將通過將執行實際繪圖操作的畫布物件發出渲染命令。

1.4 版為渲染器引入了一種新的渲染能力,它基於離屏表面的繪製。作為一種快捷方式,我們稱之為“離屏渲染”。這提高了渲染速度,甚至是數學動畫等新功能所必需的。所以它絕對是我們推薦的選擇。

要使用此渲染,您必須實現 IRenderTarget2ICanvas2,或使用參考實現提供的 和來處理離屏表面的繪製請求。

1.4 版本之前的傳統渲染模式稱之為“直接渲染”,使用它必須實現 IRenderTargetICanvas

iink SDK 1.4 渲染器仍然相容直接渲染的傳統渲染模式。

3.4 層

出於效能原因,渲染器在兩個不同的層上工作。兩層分別是:

  • 一個模型層,對應於模型中已經被引擎處理過的所有內容(指南、筆畫、影像、排版文字……),
  • 一個捕獲層,渲染在螢幕上繪製但尚未被引擎處理的墨水。
    每個圖層都可以獨立於其他圖層重新整理,因此不需要重新繪製所有內容。

示例程式碼:

import com.myscript.iink.Editor;
import com.myscript.iink.Engine;
import com.myscript.iink.Renderer;

public class Calculator
{
  private Renderer renderer;
  private Editor editor;

  public Calculator()
  {
    // Previous initialization
    ...

    // Create the view
    EditorView editorView = new EditorView(context);
    editorView.setEngine(engine);
    // ... add it to the UI hierarchy ...
    ...
    // accessing renderer
    renderer = editorView.getRenderer();
    // accessing editor
    editor = editorView.getEditor();
  }
}

四、 編輯器 Editor

Editor物件是與內容互動的中心點,通過渲染目標獲取。

4.1 設定 ContentPart

// Create a new package
ContentPackage contentPackage = engine.createPackage(packageNameFile);

// Create a new part
ContentPart contentPart = contentPackage.createPart("Text");

// Accessing editor
Editor editor = editorView.getEditor();

// Associate editor with the new part
editor.setPart(contentPart);

在設定部件之前 ,您必須確保之前已呼叫 editor.setViewSize() 並將字型度量提供程式附加到編輯器。如果使用參考實現,呼叫 editorView.getEditor() 將自動完成。

4.2 輸入捕獲

4.2.1

遵循Interactive Ink互動模式,iink API 認為筆事件專用於書寫或編輯內容(文字、數學或形狀內容、編輯手勢等),觸控事件來操作內容(選擇、拖放、滾動等)。

由於您負責將事件傳播到編輯器 - 如果您的使用者使用手指或電容式觸控筆書寫,或者如果您的應用程式是圍繞一組模態工具構建的 - 您可以選擇 iink SDK 應視為筆或觸控事件。

輸入的型別由PointerType列舉定義.

您也可以使用它來選擇具有特定行為的工具(例如橡皮擦)。

4.2.2 指南

文字文件和文字部分預設設定了指南。指南為終端使用者提供了有用的提示,讓他們知道在何處書寫以及以何種大小書寫。它們還可以提高識別準確度,前提是手寫將它們用作基線。

您可以通過引擎或 編輯器配置的鍵來啟用或禁用文字部分的指南。指南之間的垂直間距可以通過文字樣式選項進行調整。text.guides.enable

如果您知道您的輸入與指南不匹配,例如來自非結構化上下文(如一張紙)的墨水,您必須禁用它們以確保良好的識別。

4.2.3 增量輸入

Interactive Ink SDK 通常實時處理使用者輸入。因此,您必須說明指標(筆、手指)如何與捕獲表面(通常是螢幕或圖形輸入板)互動。

這可以通過呼叫Editor物件的以下方法來完成:

  • pointerDown() - 當指標第一次接觸表面時。

  • pointerMove() - 當指標在保持與表面接觸的同時移動時。

  • pointerUp() - 當指標從表面抬起時。
    這些方法中的每一種都要求您提供:

  • x 和 y - 指標在表面上的座標

  • t - 指標事件的時間戳

  • f - 與事件相關的壓力資訊(在 0 和 1 之間標準化)

  • pointerType - 指標的型別(鋼筆、手指或預定義的工具,如橡皮擦:參見PointerType列舉)

  • pointerId - 該指標的識別符號。

editor.pointerDown(0.0f, 0.0f, new Date().getTime(), .7f, PointerType.PEN, 1);
editor.pointerMove(1.4f, 2.0f, new Date().getTime(), .6f, PointerType.PEN, 1);
editor.pointerUp(2.0f, 4.0f, new Date().getTime(), .5f, PointerType.PEN, 1);

您可以呼叫 pointerCancel() 讓編輯器刪除並忽略正在進行的事件序列。

備註:

  • 時間戳通常是自 1 月 1 日以來的時間(以毫秒為單位)英石, 1970. 您可以將其設定為-1,讓iink SDK 根據系統當前時間為您生成一個。
  • Interactive Ink SDK 不使用壓力資訊。它儲存在模型中,可以在匯出時或在實現自己的墨跡書寫時檢索。如果您沒有或不需要此資訊,則可以將其設定為 0。
  • 如果只有一個指標同時處於活動狀態,則可以傳遞一個指標 id 為 -1。

在最簡單的情況下,您可以編寫如下內容:

final long NO_TIMESTAMP = -1;
final float NO_PRESSURE = 0.0f;
final int NO_POINTER_ID = -1;

editor.pointerDown(0.0f, 0.0f, NO_TIMESTAMP, NO_PRESSURE, PointerType.PEN, NO_POINTER_ID);
editor.pointerMove(1.4f, 2.0f, NO_TIMESTAMP, NO_PRESSURE, PointerType.PEN, NO_POINTER_ID);
editor.pointerUp(2.0f, 4.0f, NO_TIMESTAMP, NO_PRESSURE, PointerType.PEN, NO_POINTER_ID);

4.2.4 事件序列

在某些情況下,您可能希望一次性將一組筆畫傳送到引擎,例如,如果您從 iink 模型外部匯入要作為單個批次處理的墨水。

對於除“文字文件”之外的所有型別的部分,iink SDK 提供了一種方法,可以一次性輸入一系列指標事件pointerEvents(),這些事件以PointerEvent物件陣列為引數。

下面是一個例子:

ArrayList<PointerEvent> events = new ArrayList<PointerEvent>();

// Stroke 1
events.add(new PointerEvent().down(184.f, 124.f));
events.add(new PointerEvent().move(184.f, 125.f));
events.add(new PointerEvent().move(184.f, 128.f));
events.add(new PointerEvent().move(184.f, 133.f));
events.add(new PointerEvent().move(184.f, 152.f));
events.add(new PointerEvent().move(184.f, 158.f));
events.add(new PointerEvent().move(184.f, 163.f));
events.add(new PointerEvent().move(183.f, 167.f));
events.add(new PointerEvent().move(183.f, 174.f));
events.add(new PointerEvent().move(183.f, 183.f));
events.add(new PointerEvent().up(183.f, 184.f));

// Stroke 2
events.add(new PointerEvent().down(150.f, 126.f));
events.add(new PointerEvent().move(151.f, 126.f));
events.add(new PointerEvent().move(152.f, 126.f));
events.add(new PointerEvent().move(158.f, 126.f));
events.add(new PointerEvent().move(166.f, 126.f));
events.add(new PointerEvent().move(184.f, 126.f));
events.add(new PointerEvent().move(190.f, 128.f));
events.add(new PointerEvent().move(196.f, 128.f));
events.add(new PointerEvent().move(200.f, 128.f));
events.add(new PointerEvent().move(207.f, 128.f));
events.add(new PointerEvent().move(208.f, 128.f));
events.add(new PointerEvent().up(209.f, 128.f));

// Feed the editor
editor.pointerEvents(events.toArray(new PointerEvent[0]), false);

在呼叫 pointerEvents() 處理大量筆畫時,應將 processGestures 引數設定 false 為明確防止手勢檢測並獲得更好的效能。

對於“文字文件”部分的特殊情況, 您應該:

  1. 根據您要處理的內容型別,將每批指標事件傳送到專用的“文字”、“數學”、“圖表”或“繪圖”部分。如果您不能確保它們與墨跡單詞的基線相匹配,請記住禁用“文字”部分上的指南。
  2. 呼叫 waitForIdle() 以確保識別完成。
  3. 將塊貼上到“文字文件”部分的適當位置。

將“文字”部分貼上到“文字文件”時,iink SDK 會嘗試自動將手寫內容調整為指南。

4.3 編輯和裝飾手勢

Interactive Ink SDK 支援所有定義為Interactive Ink一部分的標準手勢。

沒有什麼特別可以從手勢中受益。SDK 將負責檢測和應用來自提供的輸入的手勢效果,無需任何管道。

裝飾可以設定樣式,並在生成一些匯出格式時考慮在內(例如:文字下劃線將被簡單的文字匯出忽略,在匯出的情況下將變為粗體並在docx匯出中進行語義標記jiix)。

4.4 其它編輯操作

可以通過Editor物件直接對部件的內容進行以下操作:

  • 撤消/重做:撤消/重做堆疊處理的 iink 操作是修改模型的操作。此類操作包括新增筆畫、應用手勢、轉換內容。撤消/重做行為取決於內容型別:

    1. 對於“文字”、“文字文件”和“繪圖”,它是基於筆畫的。
    2. 對於“圖表”和“原始內容”,它基於會話。一個會話對應於在 500 毫秒超時後被一起識別的一組筆畫。
    3. 對於“數學”,預設模式基於筆畫,但由於該math.undo-redo.mode屬性,可以將其設定為會話。
  • Clear應用於整個“ContentPart”。

然而,大多數操作都是在內容塊上完成的。

4.5 識別反饋

UI 參考實現帶有一個“智慧指南”元件,可讓您向終端使用者提供實時文字識別反饋,並允許他們從引擎中選擇替代解釋。

有關詳細資訊,請參閱描述如何使用文字識別候選項的頁面。

4.6 監控模型的變化

在某些情況下,通知模型中發生的事情是有意義的。例如,您可能希望更新介面的撤消/重做按鈕的狀態,或者僅在有要匯出的內容時才允許匯出。

您可以呼叫 addListener()removeListener() 分別附加和刪除您選擇的實現該 IEditorListener 介面的物件:該 contentChanged() 方法將告訴您模型中何時發生任何更改。

IEditorListener 還提供了一種 onError() 方法,您可以實現該方法以在出現任何問題時收到通知。強烈建議實施它,因為它允許檢測一些常見問題,例如引擎未發現的識別資產或配置。

此外,Editor 該類還提供了其他有用的方法/屬性,例如:

  • isIdle() - 如果引擎正在進行的任何處理結束,則返回true
  • waitForIdle() - 阻塞執行緒直到引擎空閒。它允許在匯出、轉換或操作內容之前等待識別完成。

isIdle()在 contentChanged() 通知中返回始終為 false。
為避免死鎖,請勿 waitForIdle() 從 IEditorListener 通知內部呼叫。

4.7 塊管理

4.7.1 ContentBlock

ContentBlock 是該內容的語義細分,並且可以包含資料或其它模組。它有一個唯一的 id、一個定義的型別(“文字”、“數學”、“圖表”、“繪圖”、“原始內容”、“容器”……)和一個邊界框。

例如:

  • “數學”部分將只包含一個塊,託管數學內容本身。
  • “文字文件”部分會更加複雜,因為它可以包含文字段落、數學方程、圖表和繪圖,以複雜的佈局排列,有時一個接一個,有時一個一個。這是“容器”塊可用於在語義上將子塊組合在一起的地方。

下圖顯示了這些不同的塊如何在其父部件內相互關聯:
contentblock

當在配置中diagram.enable-sub-blocks設定為true時,“圖表”塊包含描述圖表內容的“文字”、“節點”、“邊”或“多邊”型別的子塊。

4.7.2 ContentBlock層次結構

不同的塊形成一個層次結構,可以通過呼叫getRootBlock()父部分的方法來獲得根。反過來,每個塊都有一個getChildren()方法,將返回其自己的子項(如果有),以及一個getParent()將返回其父項的方法。

重要的是要注意塊層次結構僅在給定的時間點有效。例如,在文字文件的情況下,插入新塊、使用手勢刪除文字段落等是一些可能使您之前檢索的塊層次結構無效的事件示例。

您可以通過呼叫其isValid()方法來檢查 ContetBlock 是否仍然有效。或者在 Editor 物件上通過使用 IEditorListenerEditor 監聽 contentChanged() 事件將提供您的塊可能已失效的提示(受影響塊的 id 列表在引數中提供)。

4.7.3 新增ContentBlock

您建立的任何部分都將包含一個 rootBlock。

但是,您可以使用 editor 的 addBlock() 方法在相容部分的給定位置新增新塊,作為匯入內容的一種方式 (目前只有“文字文件”部分支援此功能)。

專用方法 addImage() 允許您在文字文件部分中插入影像。

4.7.4 ContentBlock操作

某些操作可以使用塊進行,其粒度比單一部件級別更精細:

  • hitBlock()讓您知道給定位置的最頂層塊(如果有)。例如,它可以知道使用者點選或按下了哪個塊。
  • removeBlock() 允許您刪除非根塊。
  • convert() 允許您轉換給定塊內的墨水。
  • export_() 允許您匯出特定塊的內容,包括其子項。
  • copy() 允許您將塊複製到內部剪貼簿中。然後,您可以使用 將其貼上到給定位置 paste(),就像新增新塊一樣。文字從各種文字塊源(“文字”、“圖表”或“原始內容”)複製和貼上到“文字文件”或“文字”部分。在後一種情況下,在執行貼上之前部件必須是空的。
  • 您可以監控影響給定 ContentBlock 的事件,因為有關受影響 ContentBlock 的資訊是通過介面 IEditorListener 的 contentChanged() 方法提供給您的。

4.8 編輯器級別的配置

雖然 iink SDK 可以在引擎級別全域性配置,但可以在編輯器級別覆蓋此配置。這在類似表單的用例中特別有用,在這些用例中您需要操作具有不同配置的欄位。

您可以通過呼叫 getConfiguration() 和設定應該覆蓋全域性配置的鍵的值來訪問特定編輯器的配置,以類似級聯的方式。您未在編輯器級別明確設定的鍵值仍遵循引擎級別配置。

例如:

Configuration globalConfig = engine.getConfiguration();
Configuration editorConfig = editor.getConfiguration();

// Global configuration values apply ...
String globalUnit = globalConfig.getString("math.solver.angle-unit"); // -> "deg"
String editorUnit = editorConfig.getString("math.solver.angle-unit"); // -> "deg"
globalConfig.setString("math.solver.angle-unit", "rad");
globalUnit = globalConfig.getString("math.solver.angle-unit");        // -> "rad"
editorUnit = editorConfig.getString("math.solver.angle-unit");        // -> "rad"

// ... except if overridden at editor level
editorConfig.setNumber("math.solver.fractional-part-digits", 4);
globalConfig.setNumber("math.solver.fractional-part-digits", 2);
Number editorDigits = editorConfig.getNumber("math.solver.fractional-part-digits"); // -> 4
Number globalDigits = globalConfig.getNumber("math.solver.fractional-part-digits"); // -> 2

通過呼叫適當的Editor方法來響應使用者操作來方便地完成插入撤消、重做和清除:

public void undo()
{
  editor.undo();
}

public void redo()
{
  editor.redo();
}

public void clear()
{
  editor.clear();
}

五、轉換

在 iink SDK 術語中,“轉換”是指將一些手寫內容替換為乾淨的排版等效內容。

5.1 轉換與識別

轉換是一個顯式操作,您可以觸發以使用排版等效項替換墨跡內容。它不同於它所依賴的識別過程,後者在後臺執行並解釋任何輸入,使其具有互動性。

您可以通過呼叫 editor 物件的 convert() 方法來轉換任何 ContentBlock。

5.2 轉換的目標狀態

呼叫 convert() 時,您需要提供想要達到的目標狀態。

  • DigitalPublish - 排版內容,適合出版(小字型,在文字文件的情況下適合圖形),
  • DigitalEdit - 排版內容,適合編輯(字型大小足以編輯,擴充套件圖形)。

您可以在 DigitalPublish 和 DigitalEdit 狀態之間來回轉換。

5.3 計算字型度量

要轉換您的文字內容,iink SDK 需要有關於您使用的字型的資訊,這本身取決於您的樣式選項。為此,它需要您將IFontMetricsProvider使用setFontMetricsProvider().

由於實現字型度量提供程式是一項相當棘手的任務,MyScript 通過 iink SDK 示例儲存庫為您提供 參考實現。

如果您依賴參考實現,在編輯器檢視中註冊編輯器會自動將字型度量提供程式附加到您的編輯器。

應用例項:

比如數學部分由單個塊(託管數學內容)組成,您可以使用 editor.getRootBlock() 選擇它。如果按下 UI 的“解決”按鈕呼叫物件的 solve() 方法。

editor.convert(editor.getRootBlock(), ConversionState.DIGITAL_EDIT)

由於在處理數學內容並且沒有停用求解器,如果所需條件到位,轉換數學將觸發求解。

六、匯入匯出

Interactive Ink SDK 區分序列化/反序列化(以快速且節省空間的方式儲存模型的全部內容,以供 SDK 將來重用)和匯入/匯出(作為與其他應用程式交換 iink 內容的一種方式)。

6.1 匯入內容

6.1.1 匯入 ContentBlock

您可以將資料匯入ContentBlock。例如,以下程式碼將“Hello iink SDK”文字字串匯入“Text”部分:

ContentPart textPart = ... // Get the part
editor.setPart(textPart);
editor.import_(MimeType.TEXT, "Hello iink SDK", editor.getRootBlock());

在這種情況下,您可以省略指定塊。由於該部分僅託管單個根塊,因此 iink SDK 可以自行確定將內容匯入到何處:

editor.import_(MimeType.TEXT, "Hello iink SDK", null);

對於可以承載多個塊的部件,例如“文字文件”部件,您需要明確指定目標塊。如果還不存在,可以呼叫addBlock()直接傳資料匯入。

可以通過呼叫getSupportedImportMimeTypes()編輯器來獲取給定塊支援的 MIME 型別列表。例如:

MimeType[] supportedMimeTypes = editor.getSupportedImportMimeTypes(editor.getRootBlock());

匯入內容是“破壞性的”:預先存在的內容將被清除和替換(從 JIIX 匯入的文字資料除外)。

6.1.2 JIIX 文字匯入

對於文字資料,JIIX 匯入目前僅限於文字詞候選更改。後續將提供更多匯入功能。

要更改給定文字、原始內容或圖表塊中的候選文字:

  1. 將塊匯出為 JIIX 格式。
  2. 用列表中的另一個詞替換label目標的。wordcandidates
  3. 將修改後的 JIIX 資料匯入到您的塊中。

只有在目標塊自匯出後未修改的情況下,才能匯入 JIIX 資料。

6.1.3 JIIX 匯入圖表和原始內容部分

將 JIIX 匯入“圖表”和“原始內容”部分時,執行的操作取決於配置的屬性 diagram.import.jiix.actionraw-content.import.jiix.action 。因此,您應該根據需要調整它們:

  • 當屬性被設定為update,如所描述iink改變文字候選。這是“圖表”部件的預設操作。

  • 當該屬性設定為add或 時replace,iink 會匯入墨跡資料:筆畫、字形和圖元。請注意,這不會重新注入識別結果,並且 iink 會觸發新的識別。

本段的其餘部分適用於add和replace操作。兩種操作之間的區別在於,在這種replace情況下,iink 執行清除操作,即在匯入墨跡資料之前從部件中刪除所有內容。“原始內容”部分的預設操作是add。

為了簡化給定位置的墨跡資料的匯入,您可以在 jiix 中新增一個“轉換”鍵,以將此轉換應用於 jiix 資料。json 語法是“轉換”:[xx, yx, tx, xy, yy, ty]。您可以檢視Transform API 瞭解有關轉換元件的詳細資訊。

目前在變換中只允許平移和正比例,因此 xy 和 yx 應設定為 0,xx 和 yy 應設定為 > 0

讓我們以以下用例為例。想象一下,您想將一個文字節點的大小加倍到一個“圖表”部分。以下是如何進行:

  • 將“圖表”部分匯出為 JIIX 格式。
...
{
   "type": "Text",
   "parent": 183,
   "id": 190,
   "bounding-box": {
    "x": 58.4949799,
    "y": 31.677475,
    "width": 10.9569321,
    "height": 5.24174881
   }, ...
  • 將具有 2 個比例因子(xx 和 yy)的變換物件插入要放大的節點。比例因子是與 (0,0) 相比完成的,因此如果您希望文字保持在同一位置的中心,請不要忘記計算翻譯值(tx 和 ty)。保留之前的示例,修改後的節點將是:
...
{
   "type": "Text",
   "parent": 183,
   "id": 190,
   "transform": [ 2, 0, -63.973446 , 0, 2, -34.298349],
   "bounding-box": {
    "x": 58.4949799,
    "y": 31.677475,
    "width": 10.9569321,
    "height": 5.24174881
   },...
  • 將修改後的 JIIX 資料匯入您的 ContentPart。

將比例應用於排版節點時,請記住,在進一步轉換時,iink 可能會修改排版大小。

6.1.4 匯入原墨

要匯入原始墨水內容,請例項化編輯器並向其傳遞指標事件陣列。請注意,在這種情況下,除非您正在處理“繪圖”部分,否則識別引擎將自動處理新筆畫。此方法記錄在本指南的Editor 部分。

6.2 匯出內容

6.2.1 確保識別完成

識別有時需要一定的時間才能完成,尤其是當您一次向編輯器傳送許多筆畫時。

如果要確保匯出最終識別結果,則必須在呼叫 export_() 之前呼叫 waitForIdle()

6.2.2 選擇要匯出的內容

匯出操作是在內容塊上進行的。例如,這允許您從文字文件部分匯出特定圖表。

您可以通過呼叫 getSupportedExportMimeTypes() 編輯器來檢索給定塊支援的匯出 mime 型別列表:

MimeType[] supportedMimeTypes = editor.getSupportedImportMimeTypes(block);

要匯出內容,請呼叫export_()編輯器物件的方法,將要匯出的塊和所需的 MIME 型別傳遞給它:

// Export a math block to MathML
String result = editor.export_(mathBlock, MimeType.MATHML);
// Export a text document to docx
editor.export_(textDocBlock, new File("export.docx"), MimeType.DOCX, imageDrawer);

如果 iink SDK 能夠從副檔名中明確猜測它,則 API 提供了一種方便的方法,允許您省略 mime 型別:

// Export a text document to docx
editor.export_(block, new File("export.docx"), imageDrawer);

您可以呼叫 getFileExtensions() 來獲取給定 mime 型別支援的副檔名。

6.2.3 圖片抽屜

某些格式要求您提供一個實現IImageDrawer介面的物件,以便 iink SDK 從內容生成影像。對於png和jpeg匯出來說,這是預期的情況,。

但對於docx, MyScript 提供了一個預設的、隨時可用的影像抽屜實現,作為UI 參考實現的一部分。

如果格式不需要影像抽屜,您可以提供帶有空指標的匯出方法。

要了解哪些格式需要影像抽屜,詳情請閱讀匯入和到處格式部分。

6.2.4 文字與二進位制匯出

文字格式匯出作為字串返回,然後可以以程式設計方式操作。另一方面,二進位制格式作為檔案儲存在磁碟上您可以指定的位置。

IImageDrawer imageDrawer = new ImageDrawer();
imageDrawer.setImageLoader(editorView.getImageLoader());

editor.export_(editor.getRootBlock(), new File("out/export.docx"), imageDrawer);

您可以呼叫 MimeType 物件的 isTextual() 方法來了解格式是文字格式還是二進位制格式

6.2.5 應用特定配置

某些匯出功能可讓您臨時“覆蓋”當前編輯器配置以滿足特定匯出的需要。如果您想在不影響全域性或編輯器配置的情況下調整匯出引數(例如要匯出到 JIIX 的資訊型別),這將非常有用。

以下示例顯示如何將塊識別結果匯出為 JIIX,而不包含原始墨水資訊:

// Create an empty parameter set
ParameterSet params = engine.CreateParameterSet();
// Set the appropriate configuration to exclude strokes from the export
params.setBoolean("export.jiix.strokes", false);
// Export a block with the new configuration
String jiix = editor.export_(block, MimeType.JIIX, params);

6.2.6 應用影像匯出配置

影像匯出只能應用於頁面的一部分或超過所選塊範圍。您還可以選擇是否在影像中顯示參考線。關於圖片匯出屬性的詳細資訊,請參考配置頁面

為了匯出影像,需要一個影像抽屜物件,如本節所述

以下示例說明了匯出為視口的 PNG 影像。指南是可見的:

// Create an empty parameter set
ParameterSet imageParams = engine.createParameterSet();

// Set the appropriate configuration to tune the viewport to export
float originPx = 0f;
float widthPx = 100f;
float heightPx = 200f;
imageParams.setNumber("export.image.viewport.x", originPx );
imageParams.setNumber("export.image.viewport.y", originPx );
imageParams.setNumber("export.image.viewport.width", widthPx);
imageParams.setNumber("export.image.viewport.height", heightPx);

// Set the appropriate configuration to enable the guides into the exported image
imageParams.setBoolean("export.image.guides", true);

// Create the image drawer
ImageDrawer imageDrawer = new ImageDrawer();
imageDrawer.setImageLoader(editorView.getImageLoader());

// Export the image with the customised parameters
editor.export_(editor.getRootBlock(), new File("out/ImageFileName.png"), MimeType.PNG, imageDrawer,imageParams);

6.3 支援的匯入匯出格式

6.3.1 交換格式

Interactive Ink SDK 定義了自己的格式,稱為JIIX(JSON Interactive Ink eXchange 格式的縮寫)。

這種格式提供了所支援的不同型別內容的一致表示,包括語義、位置、樣式 和與墨水相關的方面。

由於其 JSON 語法,它保持可讀性並且可以輕鬆解析,使其適合與主機應用程式交換資訊或作為支援自定義匯出格式的臨時表示。

關於 JIIX 的詳情參考 JIIX 部分。

6.3.2 其它格式

Interactive Ink SDK 允許您匯入和匯出一些常用格式,例如數學內容的 LaTeX 或文字文件塊的 Docx。

完整列表參考 匯入/到處格式部分。

示例:
對於計算器示例,您已經編寫了一些程式碼,可以讓您將歷史記錄儲存到零件後設資料中並在以後檢索它。現在讓我們看看如何使用匯出功能填充歷史字串列表。

在這裡,您需要在每次計算後將一個新專案附加到歷史記錄中,從而產生一個新狀態(多次按下“=”只會註冊一個狀態)。因此,您可以改進該solve()方法:

public void solve(TreeSet<String> history)
{
  // ... Do the conversion ...

  // Add the item to the history if there was a change
  String latexStr = editor.export_(editor.getRootBlock(), MimeType.LATEX);
  if (latexStr != null && !latexStr.equals("") && (history.isEmpty()  || history.last() != latexStr)) {
      history.add(latexStr);
  }
}

當您考慮該部分的全部內容時,您可以將空指標傳遞給匯出方法的第一個引數,而不是根塊。

七 縮放和滾動

7.1 檢視變換矩陣

儘管互動式墨水本質上是數字化的,但手寫本身卻源於物理世界。使用者在使用特定行距書寫時會感到舒適,並且會根據其手寫筆的型別和質量進行不同的書寫。同樣,識別引擎不會以相同的方式解釋墨環,如果它與一個點幾乎無法區分,或者它只有一釐米寬。對於物理世界中的這種錨定,iink SDK 在內部以毫米為單位儲存其資料。

但是,您的應用程式很可能會在檢視座標中工作,並且您將依賴該單元將輸入分派到 SDK。由於您還可以指定縮放係數,因此事情可能會變得複雜。

Interactive Ink SDK 通過附加到 editor 物件的檢視變換在這兩個座標系之間建立連結。它考慮了 dpi、縮放和潛在偏移。

與大多數 iink SDK API 一樣,您不需要自己操作轉換矩陣。但是,您可以通過呼叫Renderer 的 getViewTransform()渲染器來訪問它,並使用它從一個系統轉換到另一個系統。

7.2 檢視大小

必須使用 將檢視的大小提供給編輯器 setViewSize() 。否則,嘗試將 ContentPart 附加到 editor 時將引發異常(請注意,在參考實現中,此呼叫是作為 EditorView 物件實現的一部分為您進行的)。

檢視的大小在使互動式內容能夠動態重排並調整到檢視大小方面起著重要作用。

7.3 縮放和滾動管理

通過作用於檢視轉換矩陣來管理縮放和滾動。然而,不是直接操作它,而是在渲染器物件上提供了一組方便的方法。

如果更改渲染器轉換矩陣,則需要使渲染目標無效以強制重繪。

7.3.1 縮放

您可以通過呼叫 getViewScale() 和來操縱檢視比例的絕對值 setViewScale()。比例為 2.0 會使您的內容看起來是實際情況的兩倍,而比例為 0.5 會使內容縮小兩倍。

或者,您可以通過呼叫並傳遞所需的因子來應用相對於當前比例zoom()的縮放因子。

例如:

renderer.setViewScale(2.0f); // Scale = 2.0f
renderer.zoom(4.0f);         // Scale = 8.0f

如果您正在處理捏合縮放手勢,您可能還需要指定調整縮放的點的位置。在這種情況下,zoomAt()除了要應用的縮放係數之外,還使用 並提供要考慮的點的座標。

7.3.2 滾動

滾動檢視,只是通過呼叫應用偏移的渲染 setViewOffset() 與 x 和 y 偏移的考慮絕對分量。
例如:

renderer.setViewOffset(2.0f, 3.0f);
renderer.setViewOffset(1.4f, 2.0f);

您可以呼叫 getViewOffset() 獲取當前檢視偏移量。

7.3.4 監控轉換矩陣的變化

您可能希望將偵聽器附加到渲染器以接收轉換矩陣更改的通知(例如調整您的使用者介面)。這樣的偵聽器應實現介面的 viewTransformChanged() 方法,IRendererListener 並且可以使用 addListener()removeListener() 方法分別附加到渲染或從渲染中分離。

示例:
假設你想為你的手寫計算器實現一個水平縮放滑塊,它呼叫物件 setZoomScale() 上的一個方法 Calculator,浮動值範圍在 0.25(當它在最左邊時)到 4.0(當它在最右邊時) ),以及 1.4 的中間值(當它位於中心時)。

然後,您可以按如下方式實現該方法:

public void zoom(float scale)
{
  renderer.setViewScale(scale);
  renderer.getRenderTarget().invalidate(renderer, EnumSet.allOf(IRenderTarget.LayerType.class));
}

現在,如果您啟動應用程式並將滑塊向右移動,您將放大,向左移動滑塊時將縮小。

八、主題樣式

8.1 設定主題

一個主題是樣式表影響外觀和感覺由特定呈現的內容的 Editor物件。它不特定於任何特定的內容,因此不儲存在 ContentPart 中。

相同的內容如果被配置了不同主題的兩個編輯器例項開啟,看起來會有所不同。

樣式表應作為字串傳遞給物件的 setTheme() 方法 Editor。例如,要將當前編輯器的預設墨水顏色設定為藍色,您可以編寫:

editor.setTheme("stroke { color: #0000FFFF; }");

Interactive Ink SDK 根據裝置解析度動態計算預設樣式引數,例如行高和字型大小。您可以通過設定主題來覆蓋此預設樣式:由您提供的樣式表定義的值將具有更高的優先順序。

主題更改不受 iink SDK 撤消/重做堆疊管理。 要讓您的使用者撤消或重做主題更改,您必須在整合方面對其進行管理。 有關可能的實現路徑,請閱讀如何將 iink SDK 撤消/重做堆疊與應用程式(高階)的堆疊相結合。

8.2 改變畫筆的樣式

可以設定與筆關聯的樣式,例如更改其顏色或粗細。 這在為終端使用者提供調色盤或讓其定義鋼筆工具的特徵時非常有用。

有兩種可能的方法:通過主題或通過設定動態樣式。

8.2.1 通過主題改變畫筆樣式

主題化方法包括在自定義主題中指定與不同筆配置相對應的類,並通過在編輯器上呼叫 setPenStyleClasses() 將給定樣式應用於筆工具。

第一種方法是最有效的方法,更適合為使用者提供一組固定的選擇。 然而,它將取決於主題,因此不會儲存在內容部分中。

例如:

editor.setTheme(".greenThickPen { color: #00FF00FF; -myscript-pen-width: 1.5; }");
editor.setPenStyleClasses("greenThickPen");

8.2.2 動態設定畫筆樣式

這種方法包括通過在編輯器上呼叫 setPenStyle() 直接設定鋼筆工具的樣式。

它在處理時間方面不如主題方法優化。 但是,它可以輕鬆地動態建立樣式(例如,如果您讓您的使用者構建自己的調色盤)並將儲存在內容部分中。

例如:

editor.setPenStyle("color: #00FF00FF; -myscript-pen-width: 1.5");

雖然可以隨時通過設定主題來更新內容的整體外觀和感覺,但 iink SDK 目前不提供專門修改選定元素樣式的可能性。 預計這將在未來的版本中登陸。

8.3 iink SDK 的 CSS 特效

級聯樣式表 (CSS) 是一種非常常見的宣告樣式內容的方式,例如在 Web 上。 Interactive Ink SDK 僅依賴於 CSS 的一個子集來進行樣式設定,需要牢記一些特殊性。

8.3.1 限制

有如下限制:

  • 僅支援有限的 CSS 屬性子集。
  • 支援的型別與常規 CSS 的型別不同(例如不支援 h1、p 或 div 等型別選擇器)。
  • 預設單位是 mm,並且帶有明確單位的屬性將被忽略。
  • 不支援 inherit、initial 或 unset 等關鍵字。
  • 不支援通用選擇器 (*),也不支援組合器。

8.3.2 CSS 型別

Interactive Ink SDK 公開以下型別層次結構:

  • ink - 將下面描述的所有型別分組:
  • stroke - 僅限手寫筆畫
  • glyph - 轉換後的文字字形
  • line - 轉換後的線,例如在轉換圖表時獲得
  • arc - 轉換後的橢圓弧、橢圓和圓
  • guide - 文字指南

8.3.3 內建類和屬性

詳情請閱讀 Style 部分。

8.4 在書寫時獲得的識別反饋樣式

您可以配置 iink 來調整樣式,以便在寫入“原始內容”部分時立即獲得引擎識別為文字、形狀和繪圖的反饋。 要了解如何繼續,請參閱 Style 參考。

8.5 示例

8.5.1 自定義主題

讓我們首先調整計算器的主題。有幾種可能性可以對儲存在數學部分中的元素進行語義樣式化。
數學方程1

比如,您可能希望轉換後的內容看起來更藍一些,同時保持手寫墨水的預設黑色。您還可以為數學求解器的結果選擇一種漂亮的綠色,並將字型設定為粗體(粗細為 700)和斜體。

程式碼示例如下:

editor.setTheme("glyph.math { color: #3A308CFF; }" +
                "glyph.math-solved {" +
                "  color: #1E9035FF;" +
                "  font-weight: 700;" +
                "  font-style: italic;" +
                "}");

數學方程2

在此示例中,新樣式表中定義的值覆蓋了預設內建樣式表的值。

8.5.2 畫筆顏色選項

現在讓我們假設您想為使用者提供一個調色盤,讓他們使用兩種不同的墨水顏色,其中一種是您定義的預設藍色,另一種是紅色。

有兩種方法可以繼續:使用 setPenStyle() 或在主題級別指定一組與預定義顏色對應的類,並使用 setPenStyleClasses()

當您處理預定義的顏色時,第二種方法可能是最好的,而且它也是最有效的方法。

您可以先向自定義主題新增兩個新類(每支筆一個):

editor.setTheme(".math { color: #3A308CFF; }" +
                "glyph.math-solved {" +
                "  color: #1E9035FF;" +
                "  font-weight: 700;" +
                "  font-style: italic;" +
                "}" +
                ".defaultPen { color: #3A308CFF; }" +
                ".correctionPen { color: #FF0000FF; }");

現在,對筆選項之一的選擇做出反應只是呼叫以下任一方法:

editor.setPenStyleClasses("defaultPen");

或者

editor.setPenStyleClasses("correctionPen");

九、錯誤管理

9.1 獲取錯誤通知

iink SDK 有兩種可能返回錯誤的原因:

  1. 呼叫 API 時發生異常
  2. 後臺執行緒發生錯誤時回撥 IEditorListener.onError()

9.1.1 異常

每個 API 呼叫時可能發生的異常在 API 標頭中有詳細描述

9.1.2 編輯器級別錯誤

強烈建議您將其 IEditorListener::OnError() 作為整合的一部分來實施。這個回撥提供了詳細的訊息來解釋發生了什麼。

下表列出了主要錯誤及其可能的原因:

錯誤 資訊 可能的原因/解決方法
配置錯誤 “error: no such configuration” 找不到配置檔案。檢查包含*.conf檔案的資料夾是否被引擎或編輯器配置的configuration-manager.search-path鍵引用。
“error: no such configuration bundle” 找不到配置包。檢查引擎或編輯器配置*.conf指定的檔案中是否存在具有提供名稱的包。
“error: invalid configuration type” 解析*.conf檔案時出錯。此訊息存在許多變體,每個變體都應該是不言自明的。
“error: failed to expand environment variables in placeholders ${}”,“error: invalid command “ …
無法將墨跡新增到文字文件 “ink rejected: stroke is too small (write larger)” 您傳送到零件的筆畫太小。它可能源於使用錯誤的 dpi 值來配置渲染器。
ink rejected: stroke is too large (write smaller)” 您傳送的筆畫在垂直方向上太大:文字文件部分假定墨水是寫在參考線上的,而不是跨過參考線。
“ink rejected: stroke is above first line” 墨水寫在上邊距內。
“ink rejected: cannot write on DIGITAL PUBLISH paragraphs (convert to DIGITAL EDIT)” DIGITAL_PUBLISH轉換狀態的文字塊只能接收編輯/修飾手勢,但不能輸入。您必須將它們轉換為DIGITAL_EDIT以新增額外內容。
“ink rejected: stroke is out of document bounds” 墨跡寫在頁面邊界之外。請注意,每次新增內容時,iink SDK 都會根據提供的檢視大小的高度在頁面末尾分配額外的垂直空間。
“ink rejected: stroke is too long” 行程的長度(即它的點數)超過了發動機可以處理的範圍。
匯入錯誤 “could not import JIIX: transform contains a skew or a rotation component” 您匯入了帶有包含傾斜或旋轉元件的變換的 JIIX,但現在只允許縮放和平移。
要處理的筆畫太多 “LIMIT_EXCEEDED” 您向識別引擎傳送的筆畫比它可以同時處理的筆畫多。
意外的錯誤 “INVALID_STATE” or “INTERNAL_ERROR” 識別引擎遇到意外錯誤。

9.1.3 引擎級別錯誤

錯誤 資訊 可能的原因/解決方法
無法開啟ContentPackage “error: package is already opened” 在關閉 contentPackage 之前,您應確保編輯器處於空閒狀態,並且沒有引用此 contentPackage 的物件仍然存在。

9.1.4 證照錯誤

它通常採用INVALID_CERTIFICATE列印到控制檯的訊息形式。

如果發生這種情況,請檢查:

  1. 證照沒有時間限制或仍然有效
  2. 如果您從 Developer Portal 檢索到證照,請檢查它是否是為應用程式的捆綁 ID 生成的。

9.1.5 無法識別

如果識別不起作用,您通常可以通過檢視編輯器引發的錯誤來了解根本原因:

最有可能涉及以下原因:

  • 找不到配置- 確保*.conf您引用的檔案是提供給引擎/編輯器的路徑的一部分,並且包的名稱正確。
  • 找不到識別資原始檔- 確保識別資原始檔與您的應用程式一起正確部署並被*.conf檔案正確引用。

9.1.6 識別質量差

在這種情況下要考慮以下情形:

  1. 您是否指定了正確的語言?- 檢查您的引擎或編輯器配置以及相應的*.conf檔案和選定的包。

  2. 如果您正在識別文字,是否啟用了指南?- 如果它們是,但您不依賴它們,它們可能會對識別產生負面影響。

  3. 您waitForIdle()在嘗試檢索結果之前是否致電過?- 臨時結果可能不如最終結果相關。

  4. 您在例項化渲染器時是否提供了正確的 dpi 值?- 這是為引擎提供“比例”感的基礎,如果缺乏上下文,這可以讓它區分圓形、字母“o”或點。

  5. 你傳送正確的內容嗎?- 如果沒有,您不太可能獲得預期的輸出?!

十、文字識別候選

10.1 單詞識別候選

MyScript 手寫識別嘗試正確猜測使用者在寫什麼,但輸入有時可能不明確,如下例所示:

hello

它應該被解釋為“hello”還是“bella”?作為人類,您可能會回答“你好”。這也是 MyScript 引擎在這種情況下的想法。然而,也有可能作者實際上是指“bella”、“hella”甚至“bello”……

在內部,識別引擎會考慮不同的假設,並嘗試提出最好的假設作為選定的候選詞。它還將返回其他頂級假設,例如“bella”或“bello”作為替代詞識別候選。

候選文字可以讓您幫助您的使用者輕鬆糾正潛在的識別錯誤,或者幫助實現一個搜尋引擎,該引擎可以在一定程度的容忍度下對手寫筆記進行操作。

您可以通過編輯識別配置檔案並使用 SetWordListSize 分配給所需的值來調整識別引擎應返回的最大候選詞數。

10.2 通過變成方式管理候選文字

您還可以通過自己的程式碼與候選人進行互動(這實際上是提示器的實現方式)。
假設您得到以下墨水:
candidate

如果您要求 MyScript 進行文字匯出,它只會返回“候選測試”,這對應於引擎的最佳解釋。 要訪問識別候選,您需要請求 JIIX 匯出。

結果如下(以示例為例,只保留了 JIIX 匯出的相關部分,去除了所有墨跡和邊界框資訊):

{
 "type": "Text",
 ...
 "label": "Candidate test",
 "words": [ {
   "label": "Candidate",
   "candidates": [ "Candidate", "candidate", "Candidates", "candidates" ],
   ...
  }, {
   "label": " "
  }, {
   "label": "test",
   "candidates": [ "test", "Test", "best", "tests", "Lest" ],
   ...
  } ]
}

“候選文字”解釋由頂級“標籤”鍵提供。

這個頂級識別結果被拆分成一個“詞”陣列,每個詞都有自己的標籤和候選集(詞間空間“ ”除外)。

每個單詞的“candidates”鍵允許訪問候選陣列,從最可能到最不可能排序。 預設情況下,儲存為“標籤”的選定候選是列表的第一個元素。

要選擇替代候選人,您需要:

  1. 將標籤的值更新為建議的候選者之一。
  2. 將 JIIX 內容重新匯入原始塊。

從那裡,iink SDK 將保留您的選擇,除非內容的變化導致它完全重新考慮對這部分的識別。

重要的:

  • 始終使用與現有候選者對應的值更新標籤。 這是因為否則 iink SDK 可能無法弄清楚如何將此解釋對映到原始墨水。
  • 要重新匯入內容,請確保除了“標籤”值的更改之外,您沒有更改任何內容,無論是在原始塊中還是在 JIIX 內容中。

十一、自定義墨跡

11.1 術語

在 MyScript 術語中,“墨跡繪製”是指渲染墨跡筆劃的過程。墨跡演算法通常處理筆畫點的座標、它們相關的壓力或時間戳資訊,以及筆畫樣式,例如顏色或寬度。它由兩個主要步驟組成:

  1. 包絡的計算(即筆畫的幾何形狀),
  2. 描邊本身的繪製。

雖然 iink SDK 帶有自己的內建墨跡書寫,但該工具包足夠靈活,可以讓您自定義這些步驟。

11.2 用例

如果您將 iink SDK 整合到已實現其自己的墨跡功能的應用程式中,您可能需要確保對 iink 和應用程式管理的內容使用相同的渲染演算法。

可以調整 Interactive Ink SDK 以解決以下用例:

  • 您只想影響要渲染的筆畫的形狀,並讓 iink SDK 通過撫摸或填充您定義的信封來繪製筆畫。在這種情況下,您所要做的就是實現並註冊您自己的 stroker。
  • 您還想自己繪製筆劃,例如,如果您需要繪製紋理筆劃,或者如果您依賴於 OpenGL 等渲染技術並希望使用粒子而不是填充信封。在這種情況下,除了實現自己的筆畫(iink SDK 需要它,如下節所述),您還需要將資訊儲存到生成的路徑中,以便能夠使用它們來自己渲染筆畫。

這種墨跡定製應該特別小心,因為修改預設實現可能會大大降低渲染效能。

11.3 iink SDK 的要求

獨立於您打算應用於筆畫渲染的自定義量,iink SDK需要儘可能準確地瞭解您打算繪製的筆畫的包絡。

一個信封對應於筆畫的幾何形狀,如下圖所示:
inking

除其他外,瞭解筆畫的包絡可以讓 iink SDK 優化渲染操作(通過計算區域以重新整理模型中的變化),知道在給定的時間點渲染哪些專案並管理選擇的幾何形狀。精確的包絡定義是獲得良好結果的關鍵。

11.4 如何使用 API

要定義自己的筆觸,您應該:

  1. 實現IStroker介面:它stroke和isFill方法分別讓你返回給定筆畫的信封作為一個IPath例項,並選擇 iink SDK 是填充還是隻對結果路徑進行描邊。
  2. 實現IStrokerFactory介面:該createStroker()方法將讓您返回自定義筆劃的例項。
  3. 例項化您的自定義司爐的工廠,並與註冊它IRenderer的介面registerStroker()方法(你可以登出以後再使用 unregisterStroker()方法上的IRenderer介面)。此方法還可讓您指定與筆劃對應的畫筆名稱。
  4. 使用您定義的畫筆名稱來設定墨水樣式。

要自己渲染筆畫,您可能需要有自己的實現IPath來儲存通過介面的#{strokeMethodName}方法提供的筆畫資訊IStroker。IPath然後,儲存在物件中的資料將可ICanvas drawPath()用於您以自定義方式繪製筆畫的方法。

通過 UI 參考實現為每個目標平臺提供了特定於平臺的 IPath 介面實現。 您可以對其進行改編或子類化以儲存筆畫資訊。

253 / 5000
翻譯結果
請注意,根據您是否關閉路徑以及使用 isFill() 返回的值,您將定義不同的信封。 下圖以黑色顯示取決於選項和紅色路徑狀態的結果信封:
ink

如您所見,如果您選擇在 isFill() 方法中返回 false,iink SDK 將考慮比您的路徑嚴格返回的更大的包絡,因為它會考慮筆畫的畫素寬度(寬度引數 方法)來描邊路徑。

11.5 程式碼片段

作為示例,本節展示瞭如何基於“line to”指令實現一個非常基本的墨跡演算法。 如果您有興趣開發自己的墨跡書寫方式,您可能會實現更智慧和更好看的東西,但程式碼的結構將是相似的。

自定義筆畫
讓我們從實現 CustomStroker 類開始:

public class CustomStroker implements IStroker
{
    @Override
    public boolean isFill()
    {
        return false;
    }

    @Override
    public void stroke(InkPoint[] input, float width, float pixelSize, IPath output)
    {
        output.moveTo(input[0].x, input[0].y);
        for (int i=1; i< input.length; i++)
        {
            output.lineTo(input[i].x, input[i].y);
        }
    }
}

此處,程式碼利用 iink SDK 將繪製未填充、非閉合路徑的事實來簡化程式碼。 但是,在大多數情況下,您需要管理閉合路徑以建立漂亮的筆畫形狀。 InkPoint 結構包含有關筆畫點的所有必需資訊。

現在,讓我們實現自定義筆畫工廠:

public class CustomStrokerFactory implements IStrokerFactory
{
    @Override
    public IStroker createStroker()
    {
        return new CustomStroker();
    }
}

您現在要做的就是例項化您的工廠並使用適當的畫筆名稱將其註冊到渲染器:

CustomStrokerFactory strokerFactory = new CustomStrokerFactory();
renderer.registerStroker("LineToBrush", strokerFactory);

最後,您可以在編輯器的主題中設定筆畫:

editor.setTheme("stroke { -myscript-pen-brush: LineToBrush; }");

就是這樣! 從現在開始,iink SDK 將依賴您的自定義筆觸來呈現墨水筆觸!

自定義描邊
首先,如上所述實現自定義筆劃以生成路徑。 如前所述,即使您自己繪製筆畫,iink SDK 也需要用於各種任務的筆畫包絡。

接下來,建立您自己的 IPath 實現(您可以修改或子類化 MyScript 提供的實現),以便您可以使用它來儲存點座標、壓力等資訊,稍後您將需要這些資訊來渲染您的筆劃。 如果筆畫從模型中刪除,iink SDK 會通過其內部快取為您保留這些資訊並正確釋放記憶體。

這是一個可能的實現,基於 UI 參考實現中的 Path 類:

public class CustomPath extends Path
{
    private InkPoint[] inkPoints;
    private float width;

    public float getWidth()
    {
        return width;
    }

    public void setWidth(float width)
    {
        this.width = width;
    }

    public InkPoint[] getInkPoints()
    {
        return inkPoints;
    }

    public void setInkPoints(InkPoint[] inkPoints)
    {
        this.inkPoints = inkPoints;
    }
}

您現在可以更新您的自定義筆劃實現以將資訊儲存到路徑中:

public void stroke(InkPoint[] inkPoints, float width, float pixelSize, IPath output)
{
    CustomPath customPath = (CustomPath)output;
    customPath.setInkPoints(inkPoints);
    customPath.setWidth(width);
}

您需要更新(或子類化)提供的 ICanvas 實現以構建自定義路徑:

@Override
public final IPath createPath()
{
  return new CustomPath();
}

現在將使用我們的路徑呼叫 ICanvas 的 drawPath() 方法,您可以訪問儲存的值並以您想要的方式繪製筆劃:

public void drawPath(IPath path)
{
    // Retrieve stored information
    CustomPath customPath = (CustomPath)path;
    InkPoint[] inkPoints = customPath.getInkPoints();
    float width = customPath.getWidth();

    // Custom drawing
    ...
}

如果您想支援紋理筆觸,您可以為您支援的每個紋理註冊一個自定義筆觸,併為您的自定義 drawPath() 實現儲存必要的資訊以將其考慮在內。

確保筆劃完全繪製在信封內。 不這樣做可能會導致渲染問題。

十二、自定義識別

12.1 為什麼要自定義識別

MyScript Developer Portal 允許您下載識別資產以支援多種語言以及數學和圖表用例。每個包都帶有可在大多數情況下使用的即用型配置。

但是,在某些情況下,您可能需要調整這些提供的配置:

  • 您需要引擎識別一些未包含在預設 MyScript 詞典中的詞彙,例如專有名詞。在這種情況下,您可以構建和附加自定義 lexicon。
  • 您使用數學應用程式針對不同的教育水平,並希望限制 MyScript 可以識別的符號數量:這將減少一些可能的歧義(許多數學符號非常相似)並改善整體使用者體驗。在這種情況下,-您可以構建並附加自定義數學語法。
  • 您正在構建表單應用程式並希望減少某些欄位以僅接受某些型別的符號,例如字母數字符號、數字甚至大寫字母。在這種情況下,請考慮構建和附加子集知識。
  • 您需要向終端使用者提供或多或少的識別候選,或者您計劃為搜尋目的索引識別結果並只想考慮前 n 個候選。您可以相應地編輯配置。

12.2 識別資源

資源是可以附加到識別引擎以使其能夠識別給定語言或內容的知識片段。

12.2.1 字母知識

一個字母知識(AK)是一種資源,能夠使發動機識別單個字元對於給定的語言和給定的寫作風格。預設配置包括每個受支援語言的草書 AK。

您一次只能將一個 AK 連線到引擎。

12.2.2 語言知識

一個語言知識(LK)是為使用者提供了一個給定的語言的語言資訊的引擎的資源。它允許識別引擎通過偏愛其詞典中最有可能出現的詞來提高其準確性。預設配置包括每個受支援語言的 LK。

LK 不是強制性的,但不附加 LK 通常會導致精度顯著下降。如果您不希望寫出完整的有意義的單詞,例如,如果您打算過濾包含幾個字母的列表,則它可能是相關的。

所有語言的預設配置,但英語變體還附加了一個“輔助英語”LK,允許引擎識別目標語言和英語的混合。除了這種特殊情況,預計不會將語言混合在一起。

12.2.3 詞典

一個詞彙是一種資源,可以在除了包括哪些內容分為語言知識資源識別表中的單詞。

您可以構建和附加自己的自定義詞典。

12.2.4 子集知識

一個子集知識(SK)是制約該發動機應嘗試識別的文字數的一個資源。因此,它對應於對 AK 資源的限制。它在表單應用程式中很有用,例如,將電子郵件欄位的授權字元限制為字母數字字元、@ 和一些允許的標點符號。

您可以構建和附加您自己的自定義子集知識。

12.2.5 數學語法

一個數學語法是制約數學符號和規則引擎必須能夠處理數量的資源。在教育用例中,將識別調整到給定的數學水平(例如,僅適用於學生的數字和基本運算子)被證明非常有用。

您可以構建和附加自己的自定義數學語法。

12.3 配置檔案

12.3.1 角色

如指南的執行時部分所述,iink SDK 使用配置檔案,這是一種提供正確引數和知識以識別特定型別內容的標準化方式。

您從 Developer Portal下載的每個識別資產包都包含其自己的配置檔案。

12.3.2 部署和使用

要部署和使用配置,您需要:

  1. 將*.conf檔案與您的應用程式以及它引用的所有資原始檔一起部署(確保所有路徑都正確)。
  2. 將包含該*.conf檔案的資料夾新增到configuration-manager.search-path金鑰的引擎配置中儲存的路徑。
  3. 根據內容型別,設定正確的配置鍵。例如,要識別文字(在“Text”、“Diagram”和“Text Document”部分),您需要確保text.configuration.bundle和text.configuration.name鍵的值與您的文字配置包和配置項名稱匹配(參見下面的示例)。

12.3.3 句法

配置檔案是帶有*.conf副檔名的文字檔案。它由一個標題(標識一個配置包)和一個或多個命名的配置項(定義配置名稱)組成,由空行分隔。

下面是一個例子:

# Bundle header
Bundle-Version: 1.4
Bundle-Name: en_US
Configuration-Script:
  AddResDir ../resources/

# Configuration item #1
Name: text
Type: Text
Configuration-Script:
  AddResource en_US/en_US-ak-cur.res
  AddResource en_US/en_US-lk-text.res
  SetTextListSize 1
  SetWordListSize 5
  SetCharListSize 1

# Configuration item #2
Name: text-no-candidate
Type: Text
Configuration-Script:
  AddResource en_US/en_US-ak-cur.res
  AddResource en_US/en_US-lk-text.res
  SetTextListSize 1
  SetWordListSize 1
  SetCharListSize 1

說明:

  • 以#和開頭的行!被視為註釋並被忽略。
  • 以空格開頭的行是連續行。在這裡,幾個命令聚集在Configuration-Script.
  • 提供的值Bundle-name是包的名稱。這是 iink SDK 期望的text.configuration.bundle 配置鍵的可能值。在這個例子中,它將是en_US.
  • 提供的值Name定義了一個配置項。這是 iink SDK 期望作為text.configuration.name 配置鍵的可能值的這些名稱之一 。在這個例子中,它可以是textand text-no-candidate。在任何時間點,給定的引擎只能為每種型別的識別器配置一個配置項。
  • 對於可能的值Type重點是:Text,Math,Shape和Analyzer。它們對應於核心 MyScript 技術能夠識別的內容型別。

下表列出了您需要為 iink SDK 提供的配置項型別以支援其不同的內容型別:

內容型別 需要的配置項型別
Text Text
Math Math
Diagram Text+ Shape+Analyzer
Drawing 沒有任何
Text Document Text+ Math+ Shape+Analyzer
Raw Content Text +Shape+Analyzer

必須用空行分隔配置項。

12.3.4 配置命令

下表列出了一些可能的配置命令(放置在 下Configuration-Script):

配置項型別 句法 論點
全部 AddResDir DIRECTORY 引擎應考慮用於資原始檔相對路徑的資料夾
AddResource FILE 要附加的單個資原始檔的名稱
文字 SetCharListSize N 1 到 20 之間的整數,表示要保留的候選字元數
SetWordListSize N 1 到 20 之間的整數,表示要保留的候選詞的數量
SetTextListSize N 1 到 20 之間的整數,表示要保留的文字候選數
  1. 如果您對原始內容啟用文字識別(請參閱配置) ↩
  2. 如果您對原始內容啟用形狀識別(請參閱配置) ↩

12.4 附加資源

Configuration-Script使用該AddResource命令在配置項部分附加資源。

例如,在en_USAK的情況下,您可以這樣寫:

AddResource en_US/en_US-ak-cur.res

確保資源路徑正確。

十三、應用程式整合撤銷/重做的堆疊整合

13.1 用例

如果您的應用程式基於 iink SDK 編輯器,則可以立即使用撤消/重做管理,如指南的編輯部分所示。

然而,您的應用程式可能更復雜,您可能需要將 iink SDK 操作整合到您自己的撤消/重做堆疊中。

13.2 撤銷/重做堆疊的概念

應用程式撤消/重做堆疊可以看作是一個狀態向量,每個狀態都由特定操作產生,但第一個除外。

相關文章