1. Core Ideas: Concepts & the Tree

Before writing any code, this chapter lays out how CallioText thinks about documents: what a concept is, why concepts come in two levels, what kinds of nodes the document tree holds, and what the editor and the printer are each responsible for. There is no code here, but every later chapter builds on this one, so it is worth reading through.

Starting from LaTeX

Anyone who has written a paper in Word remembers the taste of it: the theorems want a uniform style and consecutive numbers, and all of it is held together by hand. The bold is yours, the "Theorem 3.2" is yours; insert a new theorem before the third one and every number after it expires at once, to be fixed one by one; decide someday to restyle the theorems, and there is nothing for it but to comb through the whole document.

LaTeX users take a different route. In LaTeX, a theorem is written:

\begin{theorem}
    ...
\end{theorem}

The author says only that a theorem runs from here to there; fonts, numbers and spacing are the style system's worry, and changing the style means changing one definition. Behind this sits an idea worth keeping:

The content and structure of a document should be separate from its appearance. Authors declare structure; appearance is handled uniformly by rendering rules.

LaTeX, however, is a product of the days before the internet: its output is pages prepared for the printer, its input is code that swells along with the document. What CallioText does is carry the same idea into a graphical editor: the interaction feels close to Word, the document's internal structure is as explicit as in LaTeX, its appearance is decided by rendering rules, and the output end is free, a web page or, further on, PDF.

Concepts

The theorem above is what CallioText calls a concept. A concept is a type you define for a category of document content: theorem, proof, footnote, quotation block, divider, all of these can be concepts. Each concept specifies two things:

  1. Which parameters this kind of content has. A theorem might have a title parameter (whether it displays as Theorem, Lemma or Proposition) and a numbering-style parameter.
  2. How it is rendered. The appearance while editing and the appearance in the output are two separate definitions that do not interfere with each other.

Concepts come in two levels

A key design decision in CallioText is that concepts are split into two levels. In OOP terms, a first-class concept is like a class, and a second-class concept is like an object of that class.

First-class concept (FirstClassConcept)Second-class concept (SecondClassConcept)
Defined byThe engine, in codeAnyone, at runtime (for example loaded from JSON)
DefinesThe parameter prototype (which parameters, their defaults) and the rendering logicInherits a first-class concept; overrides some parameter defaults, or pins some parameters

A concrete example. Say a first-class concept primary stands for a passage that deserves prominent display. It defines three parameters, title, prefix and ordering, along with the rendering rule: title and number first, body after. A second-class concept named Proposition inherits primary and sets the default title to "Proposition"; another one named Project also inherits primary but displays the project's name as its title. The two share one copy of rendering code and differ only in parameters.

There are two motivations for this design:

Naming convention: first-class concepts take lowercase English names, such as primary, strong, quote; second-class concepts take real words from the author's vocabulary, such as Proposition, Emphasis, Quotation.

The document tree: seven kinds of nodes

A document is a tree. Its nodes come in seven kinds; five of them are concept nodes (each must specify which concept it uses), and two are plain content:

NodetypeRoleAllowed children
TexttextLeaf node, the actual charactersnone
ParagraphparagraphContainer of a run of texttext, inline, support
InlineinlineInline semantics: emphasis, links, inline formulastext, inline
GroupgroupBlock-level semantics: theorems, proofs, quotation blocksparagraph, group, structure, support (no bare text or inline)
StructurestructureSemantics with a fixed internal layout, such as a row split into columnsgroups only
SupportsupportContentless placeholder elements: dividers, imagesnone (placeholder empty text)
AbstractabstractAn independent sub-document attached to another node: footnotes, annotations. The document root is also one.same as group

Beyond the table, a few rules deserve a mention of their own:

The fields of a concept node

Every concept node carries a fixed set of fields. Using the group node as the example:

interface GroupNode {
    type: "group"
    idx: string
    concept: string
    parameters: ParameterList
    children: NonLeafNode[]
    abstract: AbstractNode[]
    relation: "chaining" | "separating"
}

relation deserves an example. Two adjacent list blocks can be two unrelated lists (separating), or one list that resumes after being interrupted (chaining). Rendering and numbering can both see the difference: automatic numbering, for instance, can be configured to restart at separating blocks and to continue across chaining ones. Authors can switch a block between the two states at any time in the editor.

The editor and the printer

Two parts of the library process this tree: the editor and the printer. The most fitting analogy for their relationship is a compiler. The document tree is an intermediate representation; the editor is the front end, encoding the high-level language (the author's graphical input) into that representation; the printer is the back end, translating the representation into the target language (a web page, or further on, PDF output). Front end and back end talk only through the intermediate representation, which is why the same tree can wear two fully independent appearances.

The printer has one important trait: before actually rendering, it first walks the whole tree from start to end in document order (this step is called preprocessing). The reason is straightforward: information like theorem numbers cannot be computed from a single node. The fifth theorem is number 5 only because four theorems precede it, and someone has to count them. Preprocessing computes all such document-wide information up front so that each node can simply read its own result at render time. Automatic numbering and cross references are both built on this step; chapter 6 puts it to use.

Chapter summary