This page explains how iink SDK lets you organize the data it works on, store content and load it again later.

Structure of the model

MyScript iink SDK stores content using the following structures:

The complete list of supported part types can be found here.

It is possible to get the number of parts of a given package via its PartCount property and access an individual part by calling GetPart() and pass it the 0-based index of the part to load.

Parts host the data iink SDK works on. As a result, it is required to use them even if you do not intend to persist data on the disk as part of your application workflow.

Although a content part sometimes hosts a single content block, both concepts are not equivalent. A part corresponds to a serialization unit, while a block corresponds to a semantic unit that can be manipulated.

Working with packages

MyScript iink SDK uses the file system to limit the amount of RAM that is required at a given point in time, as well as to store temporary files. It also provides serialization/deserialization methods to let application save/load MyScript data, either for persistence needs or to exchange it between users.

MyScript iink SDK serialization format is optimized for speed and compactness, but is not easily readable. If you want to parse the content of the interactive model, refer to the import/export section.

Temporary folder

Even if you do not intend to explicitly save any content, MyScript iink SDK requires a read/write access to at least one folder on the file system: the temporary folder, where it will output the intermediate files it is working on.

By default, iink SDK uses the folder where the package file is located. It may however be relevant (and even mandatory on certain platforms) to choose another folder. This can be achieved by setting the value of content-package.temp-folder in the configuration of the engine.

If it does not exist, the folder will be created if you save the corresponding package. It may also be automatically created by iink SDK in case it has a need to reduce its memory usage or when handling some particular objects such as images.

Creating and loading packages

To create or open packages, use the OpenPackage() method. It comes with different possible options:

To create a new package, you can alternatively call the CreatePackage() convenience method, which is equivalent to call OpenPackage() with the CREATE_NEW option.

Both methods take as their first parameter the name of the file corresponding to the package on the file system.

// Create a new package
var newPackage = engine.CreatePackage("newPackage.iink");

// Load an existing package
var existingPackage = engine.OpenPackage("existingPackage.iink");

Saving packages

MyScript iink SDK provides two distinct methods to save content: Save() and SaveToTemp(), both to be called on the ContentPackage instance to consider.

It is important to understand the distinction between them, as they play different roles:

The following diagram explains how iink SDK manages the memory, as well as the role of the different save operations:

ContentPackage “content.iink” (in RAM) content.iink (zip archive) Temporary folder content.iink-files fragment 1 fragment 2 fragment 3 first “save” create archive and commit content “open” partial extraction “saveToTemp” off-load memory <automatic> partial extraction <automatic> partial load or off-load “save” commit changes
By default, data corresponding to a content package is stored in a folder named after this package by appending the -files suffix and located in the same folder.

Deleting packages

Use the DeletePackage() method to remove an existing package from the disk. Make sure to first release all references to this package, including parts you opened.

Working with parts

Parts are created and manipulated from their parent package.

Creating parts

To create a part, call CreatePart() on the parent package and pass it a string corresponding to the content type you want to assign to its root block (it cannot be changed afterwards).

Possible options are currently “Text”, “Math”, “Drawing, “Diagram”, “Raw Content” or “Text Document” (case sensitive).

To copy a part from an existing package, call ClonePart() on the destination package (which can be the same as the origin package if you just want to duplicate the part) and pass it a pointer to the part to copy.

To “move” a part from a package to another, first clone it into the new package and then delete the part from the original package.

Opening parts

To open a part, call GetPart() and pass in parameter its index in its parent package (indexes start at 0). The number of parts of a given package can be retrieved via its PartCount property.

Deleting parts

To delete a part, call RemovePart() on its parent package.

Objects lifecycle

Before closing your application or opening a new package, make sure to first release all references to the corresponding objects. If you don’t, you might encounter unexpected behavior, as native resources are still allocated.

This resource management must be applied to all objects implementing the IDisposable interface: Configuration, ContentBlock, ContentSelection, ContentPackage, ContentPart, Editor, Engine, ParameterSet, RecognitionAssetsBuilder, ToolController and Renderer.

To force the immediate release of native resources, you have to explicity call the Dispose method of the corresponding objects. Make sure not to reference them somewhere else by setting their references to null as well.

For instance:

if (_editor.Part != null)
{
  var part = _editor.Part;
  var package = part?.Package;
  _editor.Part = null;
  part?.Dispose();
  package?.Dispose();
  package = null;
}

Metadata

You can attach metadata to both ContentPart and ContentPackage objects, and they will be serialized with the file. This can prove useful to store client-specific parameters.

Use the Metadata property to store and retrieve the metadata attached to the object.

// Retrieve the metadata from a part
var metadata = part.Metadata;

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

// Store the metadata back into the part
part.Metadata = metadata;

The structure representing metadata is a ParameterSet and is manipulated the exact same way as engine configuration parameters.

Back to the example

The calculator requires setting a package that iink SDK can use to store the content it is working on.

In addition, the user needs to be able to restore the state of the last session if he exits and relaunches the app. This can also prove useful on systems such as Android, where the content should be saved then restored when rotating the screen.

private ContentPart _part;
private ContentPackage _package;
private string _packageName = "calculator.iink";

public void SaveContent()
{
  // Save history in part metadata
  SaveHistory();
  // Save the content package to the file system
  _package.Save();
}

public void OpenContent()
{
  if (System.IO.File.Exists(_packageName))
  {
    _package = engine.OpenPackage(_packageName);
    _part = _package.GetPart(0);
    // Load history from part metadata
    LoadHistory();
  }
  else
  {
    // Create a content package to provide the SDK with a place to work
    _package = engine.CreatePackage(_packageName);
    // Create a Math part
    _part = _package.CreatePart("Math");
  }
}

You can make sure to call OpenContent() after launching the app to always have a package to work on.

You can also use metadata to store the history (an array of LaTeX strings):

private List<string> history;

void LoadHistory()
{
  var parameterSet = part.Metadata;
  history = parameterSet.GetStringArray("history").ToList();
}

void SaveHistory()
{
  var parameterSet = editor.Part.Metadata;
  parameterSet.SetStringArray("history", history.ToArray());
  editor.Part.Metadata = parameterSet;
}

How to actually fill the history will be left for the import/export page of this guide.

Let’s now see how iink SDK can render some content.