MyScript 开发文档

一、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 撤销/重做堆栈的概念

应用程序撤消/重做堆栈可以看作是一个状态向量,每个状态都由特定操作产生,但第一个除外。