Attributes

From LuaTeXWiki

Introduction

Attributes are a new concept introduced in LuaTeX. In a way, they are a mix between count registers (they are numbers) and fonts (they are attached to the nodes created under their scope). They allow information to be carried between different stages of processing.

Syntax

Syntactically, attributes are no different from (and can be used as) count registers. Accordingly, primitive commands to handle attribute registers mimick those for count registers: like \count, \attribute is used to refer to a register, and like \countdef, \attributedef defines a control sequence to be a synonym for a register. The syntax is:

\attribute <16-bit number> <optional equals> <32-bit number>
\attributedef <csname> <optional equals> <16-bit number>

It is wise to have a \newattribute command, like \newcount (from plain TeX, used also in LaTeX and ConTeXt), for the allocation of registers. The command exists in ConTeXt, and is available in the luatexbase package for plain TeX and LaTeX under the name \newluatexattribute.

Whenever a count register is allowed, an attribute register is too; that is, primitives like \advance, \multiply, \divide, can be used. More generally, anywhere a number is needed, attributes will do. Of course, assignments to attribute registers obey grouping.

Attributes differ from count registers in two respects:

  • They have a special value, -"7FFFFFFF in hexadecimal or -2147483647 in decimal; an attribute with that value (which is default) is said to be unset. With any larger value, the attribute is set.
  • When nodes are created, they carry with them the values of all the attributes in force at creation time (except for some asynchronous attribute settings). In other words, when inspecting a node at whatever stage of its processing, one can retrieve (and also modify) the values of the attributes when the node was created. In that respect, attributes behave like fonts (glyph nodes always carry information about their fonts).

Box attributes

When creating boxes, there exists a new keyword attr besides to and spread. The node representing the box will then have the associate attribute, but not its contents. For instance, after:

\attribute0=1
\setbox0=\hbox attr 1 = 1 {contents}

The box (as a node) and each node of its contents have value 1 for attribute 0, but only the box node has value 1 for attribute 1; its contents has whatever value is currently assigned to that attribute (very probably the attribute is unset).

The syntax for the attr keyword is the following (all spaces are optional):

attr <number> = <number>

The attr declaration(s) should precede the to or spread declaration if any.

Asynchronous attribute settings

Sometimes nodes are assigned attribute values depending not on the real values at creation time but on the values of the surrounding nodes. The following simplistic code illustrates that:

{\attribute0=1 Ve}\par

LuaTeX will insert a font kern between the two letters (if the font says so, of course). That node will have attribute 0 set to 1, even though that is no longer the real value. Indeed, the kern node is created when building the paragraph, i.e. when the \par command is encountered; at that time, attribute 0 no longer has value 1, since that value was assigned in a group and the group is closed. So the kern should normally have whatever value is in force outside the group; instead it is given the value of the nodes that prompted its creation (more precisely, the value of the first node, which can differ from the second one). Similarly, a ligature node takes the values of those nodes it is created from, as does a discretionary node.

The \leftskip and \rightskip glues, inserted when non-zero at the beginning and end of each line of a paragraph, take the value of the neighboring nodes (i.e. the first and last nodes of a line, respectively).

Those asynchronous settings are supposed to make more sense than assignments that would follow the real values. Counter-measures can easily be implemented if necessary: for instance, one can inspect all lines in a paragraph and reassign to the \leftskip and \rightskip glues the current values for the attributes.


On the Lua side

Registers

Like all registers, attributes are stored in a Lua table in the tex library. Hence besides tex.count, tex.box, etc., there exists tex.attribute. The indices to entries are either a register number or the name of a control sequence defined with \attributedef. For instance, after

\attributedef\myattribute=100
\myattribute=1

both tex.attribute.myattribute and tex.attribute[100] return 1. The entry can also be assigned to, but then the assignment is necessarily local.

There are also two functions to set or retrieve attribute values, with the following syntax:

tex.getattribute(<register>)
tex.setattribute(["global",] <register>, <number>)

In both case <register> is the same as an entry in tex.attribute: either a number or a string representing a control sequence defined with \attributedef. The first function returns the current value of the attribute, while the second sets it, either locally or globally if the first optional argument is given.

Attributes in nodes

The node library offers the following function to deal with the values of attributes for a given node:

node.has_attribute(<node>, <attribute>[, <value>])

Without the third argument, if <node> has attribute set, i.e. with a value different from -"7FFFFFFF, then the value is returned; otherwise nil is returned. If the third argument is given, the value of the attribute is returned if and only if it is equal to <value>, and nil otherwise.

node.set_attribute(<node>, <attribute>, <value>)

Sets <attribute> to <value> for <node>.

node.unset_attribute(<node>, <attribute>[, <value>])

Unsets <attribute> for <node> and returns the old value; if <value> is present, the attribute is unset only if it matches <value>.

Attribute lists

In nodes, attributes actually are implemented as nodes in a list. That is why the attr field of all nodes (except attribute nodes themselves) points to a node of type attribute_list. That node has only one field next pointing to the first attribute node in the list, if any. There might not be any such node because only set attributes are present in the list. Attribute nodes have three fields:

  • number: the attribute's number;
  • value: the attribute's value;
  • next: the next attribute in the list, i.e. an attribute node that is set.

As an example, the following code prints 0 = 5 and 10 = 45.

\setbox0=\hbox attr0 = 5 attr10 = 45 {contents}

\directlua{
  local attr = tex.box[0].attr.next
  while attr do
    texio.write_nl(attr.number .. " = " .. attr.value)
    attr = attr.next
  end
}


Examples

The idea behind attributes is to mark material at a given time so as to spot it at a later stage of processing. The example in the page on the post_linebreak_filter callback shows how attributes can be put to use for margin notes: the text to which a note is added is marked with an attribute, and later, when the paragraph has been built, the note is appended to the line containing that text, identified by its having a given value for the attribute.