手写识别 API

社区组草案报告,

此版本:
https://wicg.github.io/handwriting-recognition/
编辑:
钱杰伟 (谷歌)
参与:
GitHub WICG/handwriting-recognition (新建 issue, 开放的 issue)
提交:
GitHub spec.bs 提交记录

摘要

手写识别API使Web应用能够利用现有操作系统能力识别手写文本。

本文档状态

本规范由 Web平台孵化社区组 发布。 它不是W3C标准,也不在W3C标准化进程中。 请注意,根据 W3C社区贡献者许可协议(CLA) 可选择有限退出及附加其他条件。 了解更多 W3C社区与业务组 的信息。

1. 引言

手写输入是绘画。绘画捕获了能够数字化重现人类笔尖运动所需的信息。

本API旨在将操作系统的能力暴露给Web。我们预计手写识别能力会因操作系统而异,因此API旨在达成能够灵活集成操作系统特性的设计。

我们期望用户代理将Web API的数据结构(本规范中定义)转换为宿主操作系统可用的结构,并将Web API与操作系统API连接起来。

本API不试图定义在所有平台上行为一致的识别。

1.1. 术语定义

在本规范中,我们定义了如下概念,以手写“WEB”为例:

手写概念

手写识别器(handwriting recognizer) 是一个接口(通常由外部应用或服务实现),它:

是否为手写识别器由用户代理自行决定。

转换(convert)与手写识别器适配的数据格式,用户代理应将本规范中的定义与手写识别器的等价概念对应。

操作系统中一些可用的手写识别器有:

手写识别器还可以输出额外信息帮助Web应用更好地处理手写内容(如删除手写中的某个字符)。

切分(Segmentation)用于将字素(用户感知的字符)与其组成的笔划和点进行映射。一个字素可能跨多个Unicode码点。

x 由一个UTF-16码点组成:\u0078
g̈ 由两个UTF-16码点组成:\u0067\u0308
षि 由两个UTF-16码点组成:\u0937\u093f

以手写文本 "int" 为例:

手写切分

若应用希望删除字符"t",可以从绘画中移除第3、第4笔。

2. API惯用法

本规范中提及的任务源手写识别任务源(handwriting recognition task source)

当某个算法排入一个手写识别API任务 T时,用户代理必须使用全局对象手写识别任务源T排入当前realm记录的任务队列。

除非特别说明,通过算法步骤构造的JavaScript对象的realm应为当前realm记录

3. 能力查询

能力查询接口允许Web应用查询实现相关的能力,从而决定是否使用该功能。

const modelConstraint = { languages: ['zh-CN', 'en'] };
const modelDesc = await navigator.queryHandwritingRecognizer(modelConstraint);

// \`modelDesc\` 描述了满足 \`modelConstraint\` 的手写识别器。
// 如果约束无法被满足,\`modelDesc\` 为null。
{
  textAlternatives: true,
  textSegmentation: true,
  hints: {
    alternatives: true,
    textContext: true,
    inputTypes: ['mouse', 'touch', 'stylus']
  }
}
[SecureContext]
partial interface Navigator {
  Promise<HandwritingRecognizerQueryResult?>
      queryHandwritingRecognizer(HandwritingModelConstraint constraint);
};

dictionary HandwritingModelConstraint {
  required sequence<DOMString> languages;
};

dictionary HandwritingRecognizerQueryResult {
  boolean textAlternatives;
  boolean textSegmentation;
  HandwritingHintsQueryResult hints;
};

dictionary HandwritingHintsQueryResult {
  sequence<HandwritingRecognitionType> recognitionType;
  sequence<HandwritingInputType> inputType;
  boolean textContext;
  boolean alternatives;
};

enum HandwritingRecognitionType{
  "text", "per-character"
};

enum HandwritingInputType {
  "mouse", "stylus", "touch"
};

3.1. queryHandwritingRecognizer(constraint)

该方法为Web应用提供了一种查询底层识别器能力并决定是否使用的方法:

同一份HandwritingModelConstraint 可用于调用createHandwritingRecognizer(constraint) 去创建满足约束的HandwritingRecognizer

当调用queryHandwritingRecognizer(constraint)方法时,按以下步骤执行:
  1. 如果constraint不包含languages 成员,则返回一个被拒绝的Promise,异常类型为TypeError

  2. p一个新的Promise

  3. 按如下步骤并行运行:

    1. constraint转换为手写识别器合适的数据结构

    2. 若满足下列任何一项:

      排入手写识别API任务,以null解析p并终止剩余操作。

    3. 否则,排入手写识别API任务

      1. result为一个新的HandwritingRecognizerQueryResult

      2. 转换识别器特性描述,并填充result的所有成员。

      3. 解析presult

  4. 返回p

将数据转换为HandwritingRecognizerQueryResultHandwritingHintsQueryResult时实现应遵循:

3.2. HandwritingModelConstraint 属性

描述底层手写识别器(如后续要创建)的约束条件。

createHandwritingRecognizer(constraint)中也用于创建手写识别器。

languages
用于描述识别器要识别的语言的一组[BCP47]语言标签。

如提供多个语言,识别器需支持全部才能满足约束。

用户代理应考虑每个语言标签所有可能的书写体系。例如,只能识别拉丁字母阿塞拜疆语("az-Latn")的识别器,不应用于"az"标签,因为阿塞拜疆语也可用西里尔字母("az-Cyrl")书写。

区分书写体系时,建议使用最具体的语言标签。例如,仅需支持拉丁字母阿塞拜疆语时用"az-Latn"。

部分识别器仅支持单语种。为提升互操作性,可为每种语言创建独立识别器。

3.3. HandwritingRecognizerQueryResult 属性

描述某手写识别器实现的固有特性。

textAlternatives
布尔型,表示实现是否返回多个备选转写候选而不是单一结果。
textSegmentation
布尔型,表示实现是否为每个转写返回分段信息
hints
HandwritingHintsQueryResult 对象,描述在startDrawing()中可接受的提示。

3.3.1. HandwritingHintsQueryResult 属性

描述一组可选传入startDrawing()以提升准确率或性能的提示。

本处属性名称与startDrawing()方法中一致。

recognitionType
一组HandwritingRecognitionType 枚举,描述可能书写的文本类型。

提示不能保证转写结果符合指定类型。

"text"
普通书写语句中的自由文本,意味着图像为真实词语。例如用于日常语言句子的手写输入。
"per-character"
由独立且互无关联的字素组成的手写体(用户感知字符)。如序列号、注册码等。
inputType
一组HandwritingInputType 枚举,描述绘画是如何书写的。
"touch"
用手指绘制。
"stylus"
用手写笔绘制。
"mouse"
用鼠标光标绘画。
textContext
布尔值,表示是否接受textContext。textContext为展示给用户的文本或前置已识别文本的字符串。
alternatives
布尔值,表示是否允许设置返回备选转写的个数。用于限制getPrediction返回的最大备选数量。

4. 创建手写识别器

A HandwritingRecognizer 管理执行识别所需的资源。

const modelConstraint = { languages: ['en'] };

try {
  const recognizer = await navigator.createHandwritingRecognizer(modelConstraint);
  // Use \`recognizer\` to perform recognitions.
} catch (err) {
  // The provided model constraint can't be satisfied.
}
[SecureContext]
partial interface Navigator {
  Promise<HandwritingRecognizer>
      createHandwritingRecognizer(HandwritingModelConstraint constraint);
};

4.1. createHandwritingRecognizer(constraint) 方法

此方法创建一个满足所提供 HandwritingRecognizer 约束 HandwritingModelConstraint 的对象,并保留执行识别所需的资源。它代表了访问handwriting recognizer的入口点。

用户代理可能会提示用户安装或下载手写模型。Web 应用不应假定此方法总是会很快返回。

When createHandwritingRecognizer(constraint) 方法被调用时,执行以下操作:
  1. 如果 constraint 不包含 languages 成员,则返回一个被拒绝的 Promise,异常类型为 TypeError

  2. p一个新的 Promise

  3. 按如下步骤并行运行:

    1. constraint 转换为适合 创建平台相关 handwriting recognizer 的格式。

    2. 排入 Handwriting Recognition API 任务以执行:

      1. 如果用户代理无法创建或准备可执行识别的handwriting recognizer,则根据失败原因用新的异常 reject p

      2. 否则:

        1. result 为新的 HandwritingRecognizer 对象。

        2. result 与前一步创建的平台相关 handwriting recognizer 关联。

        3. result.active 标记为 true

        4. 解析 presult

  4. 返回 p

5. 使用识别器

const drawingHints = { textContext: "Hello world." }
const drawing = recognizer.startDrawing(textContext)

// Do something with \`drawing\`.

// Frees resources associated with recognizer.
recognizer.finish()

5.1. HandwritingRecognizer 对象

[Exposed=Window, SecureContext]
interface HandwritingRecognizer {
  HandwritingDrawing startDrawing(optional HandwritingHints hints = {});

  undefined finish();
};

dictionary HandwritingHints {
  DOMString recognitionType = "text";
  DOMString inputType = "mouse";
  DOMString textContext;
  unsigned long alternatives = 3;
};

HandwritingRecognizer 有一个 active 标志,为布尔值。active 标志初始为 true, 在调用 finish() 方法时变为 false

当识别器的 active 标志为 true 时,Web 应用可以为该识别器创建新的绘画并执行识别。

用户代理可以限制网站的active handwriting recognizer总数。

5.2. startDrawing(hints)

此方法创建一个 HandwritingDrawing 用于存储后续识别的绘画信息。

HandwritingDrawing 拥有一个 strokes,它是一个 list,包含若干 HandwritingStroke, 初始为空。

HandwritingDrawing 有一个 recognizer,用于存储创建此 HandwritingDrawingHandwritingRecognizer 的引用。

When startDrawing(hints) 被调用时,执行以下操作:
  1. 如果 this.active 标志不是 true,则 抛出 一个新的 DOMException 对象,其 name 成员为 "InvalidStateError" 并中止。

  2. 将提供的 hints 转换为适合 handwriting recognizer 的格式。

  3. 创建一个新的 HandwritingDrawing 作为 result,如有必要在其中存储转换后的提示。

  4. result.recognizer 设为 this

  5. this.strokes 设为新的空list

  6. 返回 result

如果提供的 hints 包含识别器不支持的特性,用户代理应忽略相关属性。

如果未提供 hints,用户代理可自行决定应用默认值。

用户代理可能会创建并传递转换后的 hints 给等价的 handwriting recognizer 的绘画对象,而不将其存储在 HandwritingDrawing 中。

5.3. finish()

此方法将 thisactive 标志设为 false,释放已分配的handwriting recognizer资源,并使未来涉及 this 的相关操作失败。

  1. 如果 this.active 不是 true,中止。

  2. this.active 设为 false

用户代理应释放与handwriting recognizer相关的资源。

在调用 finish() 之后,对由 this 创建的 HandwritingDrawing 调用后续的 getPrediction() 将会失败。

6. 构建 HandwritingDrawing

HandwritingDrawing 管理关于绘画的上下文信息,并维护构成绘画的笔划和点。它代表一个 drawing

用户代理可以将所有笔划和点存储在内存中,然后在调用 getPrediction() 时将它们转换为适合 handwriting recognizer 的格式。在这种情况下,HandwritingDrawingHandwritingStroke 表现得像一个列表。

或者,用户代理可以立即将每个笔划和点转换为合适格式,并在每次调用 HandwritingStrokeHandwritingDrawing 的方法时将它们传递给 handwriting recognizer。在这种情况下,HandwritingDrawingHandwritingStroke 表现为平台相关对象的包装器,用于handwriting recognizer

用户代理可以跟踪绘画的更改以提升 getPrediction() 的性能。此类更改跟踪可以通过在相关方法调用时将笔划标记为“已修改”来实现。更改跟踪使得用户代理能够对支持的 handwriting recognizer 执行增量识别。

例如,考虑一个绘画包含三段文本,之前的 getPrediction() 调用的预测结果已被存储。Web 应用随后在第三段中添加了一笔。有了更改跟踪,实现将只请求 handwriting recognizer 对第三段的笔划(例如包括被修改笔划附近的笔划)进行新的预测,并将该预测与现有预测合并。

const handwritingStroke = new HandwritingStroke()

// Add points to a stroke.
handwritingStroke.addPoint({ x: 1, y: 2, t: 0});
handwritingStroke.addPoint({ x: 7, y: 6, t: 33});

// Retrieve points of this stroke.
// Returns a copy of all the points, modifying the returned points
// has no effect.
const points = handwritingStroke.getPoints();

[ { x: 1:, t:2, t: 0 }, { x: 7, y: 6, t: 33} ];

// Delete all points in a stroke.
handwritingStroke.clear();

// Add a stroke to the drawing.
drawing.addStroke(handwritingStroke);

// Get all strokes of the drawing.
// Returns a list of \`HandwritingStroke\` in the drawing. Web applications can
// modify the stroke. For example, calling HandwritingStroke.addPoint().
drawing.getStrokes();

[ HandwritingStroke, /* ... */ ]

// Delete a stroke from the drawing.
drawing.removeStroke(handwritingStroke);

// Delete all strokes from the drawing.
drawing.clear();
[Exposed=Window, SecureContext]
interface HandwritingDrawing {
  undefined addStroke(HandwritingStroke stroke);
  undefined removeStroke(HandwritingStroke stroke);
  undefined clear();
  sequence<HandwritingStroke> getStrokes();

  Promise<sequence<HandwritingPrediction>> getPrediction();
};

[SecureContext, Exposed=Window]
interface HandwritingStroke {
  constructor();
  undefined addPoint(HandwritingPoint point);
  sequence<HandwritingPoint> getPoints();
  undefined clear();
};

dictionary HandwritingPoint {
  required double x;
  required double y;

  // Optional. Number of milliseconds since a reference time point for a
  // drawing.
  DOMHighResTimeStamp t;
};
如果 Web 应用在 HandwritingPoint 中提供了 t, 则应为给定的 HandwritingDrawing 使用共同的起点来测量所有的 t 值。

例如,可以将 t === 0 定义为调用 startDrawing() 的时间点。或者在采集点(例如在发生 touchmove 事件)时使用 Date.now()

6.1. HandwritingStroke

HandwritingStroke 表示一个 stroke。它存储了重现该次笔尖运动所需的信息。

HandwritingStroke 有一个 Points,它是一个 list,用于存储该笔划的 pointspoints 初始为空。

6.1.1. HandwritingStroke()

  1. 创建一个新的 HandwritingStroke 对象,令其为 result

  2. resultpoints 设为一个空的list

  3. 返回 result

6.1.2. addPoint(point)

此方法在被调用时向 this 添加一个 point, 执行以下操作:
  1. 如果 point 不包含 x 成员,抛出 一个新的 TypeError 并中止。

  2. 如果 point.x 不是数字,抛出 一个新的 TypeError 并中止。

  3. 如果 point 不包含 y 成员,抛出 一个新的 TypeError 并中止。

  4. 如果 point.y 不是数字,抛出 一个新的 TypeError 并中止。

  5. 如果 point 包含 t 成员,且 t 不是数字,抛出 一个新的 TypeError 并中止。

  6. p 为一个新对象,

  7. p.x 设为 point.x

  8. p.y 设为 point.y

  9. 如果 point 包含 t 成员,设 p.tpoint.t

  10. Append pthis.points

如果 point 没有 t 成员,实施不应插值或使用默认数值。实现应反映出 point.t 未被设置在 p.t 中。

在调用 addPoint(point) 之后修改 point 不会影响 HandwritingStroke

6.1.3. getPoints()

该方法返回该笔划的所有点。

调用该方法时:
  1. result 为一个新的空列表

  2. 对于每个 p

    1. 创建一个新的 对象 作为 pt

    2. pt.x 设为 p.x

    3. pt.y 设为 p.y

    4. 如果 pt 成员,则 pt.t 设为 p.t

    5. pt 添加到 result

  3. 返回 result

深拷贝可以防止对内部的修改,并支持变更跟踪

getPoints() 返回值的修改不会影响此笔划本身。

6.1.4. clear()

该方法会移除当前笔划的所有点,使其变为空笔划。

调用该方法时,
  1. 清空 this.points

6.2. HandwritingDrawing

6.2.1. addStroke(stroke)

  1. 如果 stroke 不是 HandwritingStroke 的实例, 抛出 一个新的 TypeError 并中止。

  2. stroke 的引用添加到 this.strokes

6.2.2. removeStroke(stroke)

  1. 如果 stroke 不是 HandwritingStroke 的实例, 抛出 一个新的 TypeError 并中止。

  2. 移除 this.strokes 中同对象的项(与 stroke 为同一对象)

6.2.3. getStrokes()

该方法返回该绘画中的笔划列表。

调用该方法时:
  1. result 为新的空列表

  2. 对于每个笔划 s

    1. s 添加到 result

  3. 返回 result

6.2.4. clear()

  1. 清空 this.strokes

7. 获取 HandwritingDrawing 的识别结果

// 获取该绘画笔划的识别结果。
const predictions = await drawing.getPrediction();

// \`predictions\` 是一个 \`HandwritingPrediction\` 列表,其属性
// 取决于手写识别器的能力,和 queryHandwritingRecognizer() 返回的模型描述保持一致。
//
// 例如,\`recognizer\` 支持 textSegmentation。
[
  {
    text: "hello",
    segmentationResult: [
      {
        grapheme: "h", beginIndex: "0", endIndex: "1",
        drawingSegments: [
          { strokeIndex: 1, beginPointIndex: 0, endPointIndex: 32 },
          { strokeIndex: 2 , beginPointIndex: 0, endPointIndex:: 40 },
        ]
      },
      {
        grapheme: "2", beginIndex: "1", endIndex: "2",
        drawingSegments: [
          { strokeIndex: 2 , beginPointIndex: 41, endPointIndex:: 130 },
        ]
      },
      // ...
    ]
  },
  {
    text: "he11o",
    segmentationResult: [ /* ... */ ]
  },
  // 最多返回 \`alternatives\` 个预测结果(如在 startDrawing() 里配置)。
];

7.1. getPrediction()

getPrediction() 方法返回 this 绘画的预测结果列表及其元数据。

预测结果按置信度高到低排序。如果不为空,第一个预测结果应为最有可能的结果。

如果手写识别器无法识别出任何内容,getPrediction() 应返回空列表

用户代理可能会进行变更跟踪以及增量识别以提升性能。

调用 getPrediction() 时:
  1. 如果 this.recognizer.active 不为 true,则返回一个被拒绝的 promise,其原因是 "InvalidStateError" DOMException

  2. 如果 this.strokes 为空,则返回一个解析为新建空列表的 promise。

  3. this drawing 转换为适用于手写识别器的格式。

  4. p 为新的 Promise,并 并行运行以下步骤:

    1. 将转换后的 drawing 发送给手写识别器

    2. 等待手写识别器返回其预测结果。

    3. 排入手写识别 API 任务队列以执行以下步骤:

      1. result 为列表。

      2. 对于每个返回的预测 pred

        1. pred 转为 HandwritingPrediction idl_pred

        2. idl_pred 添加到 result

      3. result 解析 p

  5. 返回 p

7.2. HandwritingPrediction 属性

HandwritingPrediction 表示来自手写识别器的预测结果。

dictionary HandwritingPrediction {
  required DOMString text;
  sequence<HandwritingSegment> segmentationResult;
};

dictionary HandwritingSegment {
  required DOMString grapheme;
  required unsigned long beginIndex;
  required unsigned long endIndex;
  required sequence<HandwritingDrawingSegment> drawingSegments;
};

dictionary HandwritingDrawingSegment {
  required unsigned long strokeIndex;
  required unsigned long beginPointIndex;
  required unsigned long endPointIndex;
};
text
表示所绘内容转录的DOMString
segmentationResult
一个HandwritingSegment列表, 将每个被识别的音位(用户感知的字符)映射到其组成的笔画与点。

如果手写识别器不支持文本分段, 则为null

Web应用可以通过textSegmentation 检查该属性是否为null。

7.2.1. HandwritingSegment 属性

HandwritingSegment 描述从绘图分段出的单个音位。

grapheme
表示音位的DOMString
beginIndex
该音位在text 中的起始索引。
endIndex
该音位在text 中的结束索引(下一个音位的起始点)。
drawingSegments
一个HandwritingDrawingSegment列表, 描述组成该音位的绘图部分。

beginIndexendIndextext 进行切片,应当得到grapheme

// Web 应用可使用 \`beginIndex\` 和 \`endIndex\` 对 \`text\` 进行切片。
// 例如,“घोषित”的 \`HandwritingPrediction\`:

const prediction = {
  // UTF-16 code points: \u0918 \u094b \u0937 \u093f \u0924
  // Graphemes: घो, षि, त
  text: "घोषित",
  segmentationResult: [
    { grapheme: "घो", beginIndex: "0", endIndex: "2" },
    { grapheme: "षि", beginIndex: "2", endIndex: "4" },
    { grapheme: "त", beginIndex: "4", endIndex: "5" },
  ]
}

// 以下均成立:
prediction.text.slice(0, 1) === "घो";
prediction.text.slice(2, 4) === "षि";
prediction.text.slice(4, 5) === "त";

// Web 应用可以通过 splice 删除第2个音位(षि)。
const withoutSecondGrapheme = (
  [...prediction.text]
    .splice(
      prediction.segmentationResult[1].beginIndex,
      prediction.segmentationResult[1].endIndex
    )
    .join('')
);
// => "घोत"

7.2.2. HandwritingDrawingSegment 属性

HandwritingDrawingSegment 描述HandwritingStroke 的一个连续片段。

属性基于HandwritingStrokeHandwritingDrawing 调用getPrediction()时的内容。

strokeIndex
HandwritingDrawingHandwritingStroke 的索引。参见strokes
beginIndex
该绘图片段的起始索引。
endIndex
该绘图片段的结束索引(下一个绘图片段的起始点)。

8. 隐私注意事项

本节为非规范性内容。

指纹向量来源于两部分:特性检测和识别器实现。

所暴露的信息量(熵)取决于用户代理的实现。我们认为没有一种方案可适用于所有场景,建议用户代理根据自身用户情况决定是否需要隐私保护措施(例如权限提示)。

特性检测可能会暴露以下信息:

可通过以下方式缓解指纹收集:

识别器实现可能暴露操作系统、设备或用户习惯的信息。这主要依赖于所用的识别器技术。

以下是一些识别器实现类型及其相关风险:

然而,目前我们尚未发现属于此类的识别器实现。但我们建议对这类模型采取隐私保护措施,或者为每次会话使用全新、干净的状态。

指纹收集成本:指纹收集方需精心设计并准备一系列手写绘图(对抗性样本)以利用模型差异。生成这些样本可能成本较高,但可以认为有动力的一方能够获得这些样本。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[BCP47]
A. Phillips, Ed.; M. Davis, Ed.. Tags for Identifying Languages. September 2009. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc5646
[ECMASCRIPT]
ECMAScript 语言规范. URL: https://tc39.es/ecma262/multipage/
[HR-TIME-3]
Yoav Weiss. 高精度时间. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行标准. URL: https://webidl.spec.whatwg.org/

IDL 索引

[SecureContext]
partial interface Navigator {
  Promise<HandwritingRecognizerQueryResult?>
      queryHandwritingRecognizer(HandwritingModelConstraint constraint);
};

dictionary HandwritingModelConstraint {
  required sequence<DOMString> languages;
};

dictionary HandwritingRecognizerQueryResult {
  boolean textAlternatives;
  boolean textSegmentation;
  HandwritingHintsQueryResult hints;
};

dictionary HandwritingHintsQueryResult {
  sequence<HandwritingRecognitionType> recognitionType;
  sequence<HandwritingInputType> inputType;
  boolean textContext;
  boolean alternatives;
};

enum HandwritingRecognitionType{
  "text", "per-character"
};

enum HandwritingInputType {
  "mouse", "stylus", "touch"
};

[SecureContext]
partial interface Navigator {
  Promise<HandwritingRecognizer>
      createHandwritingRecognizer(HandwritingModelConstraint constraint);
};

[Exposed=Window, SecureContext]
interface HandwritingRecognizer {
  HandwritingDrawing startDrawing(optional HandwritingHints hints = {});

  undefined finish();
};

dictionary HandwritingHints {
  DOMString recognitionType = "text";
  DOMString inputType = "mouse";
  DOMString textContext;
  unsigned long alternatives = 3;
};

[Exposed=Window, SecureContext]
interface HandwritingDrawing {
  undefined addStroke(HandwritingStroke stroke);
  undefined removeStroke(HandwritingStroke stroke);
  undefined clear();
  sequence<HandwritingStroke> getStrokes();

  Promise<sequence<HandwritingPrediction>> getPrediction();
};

[SecureContext, Exposed=Window]
interface HandwritingStroke {
  constructor();
  undefined addPoint(HandwritingPoint point);
  sequence<HandwritingPoint> getPoints();
  undefined clear();
};

dictionary HandwritingPoint {
  required double x;
  required double y;

  // Optional. Number of milliseconds since a reference time point for a
  // drawing.
  DOMHighResTimeStamp t;
};

dictionary HandwritingPrediction {
  required DOMString text;
  sequence<HandwritingSegment> segmentationResult;
};

dictionary HandwritingSegment {
  required DOMString grapheme;
  required unsigned long beginIndex;
  required unsigned long endIndex;
  required sequence<HandwritingDrawingSegment> drawingSegments;
};

dictionary HandwritingDrawingSegment {
  required unsigned long strokeIndex;
  required unsigned long beginPointIndex;
  required unsigned long endPointIndex;
};