Skip to content
TEI & XML Encoding

When TEI text will not line up with its facsimile, the cause is almost always one of three things: coordinates expressed in the wrong pixel space, a facs pointer that does not resolve to a real xml:id, or a facsimile element placed where the schema forbids it. Work through those three in order and you fix the great majority of image-linking faults before touching the viewer.

How is facsimile linking structured in TEI?

The model is deliberately simple. A facsimile element (sibling of text) holds one surface per page, each surface holds zone regions, and your transcription points at those zones with facs. The link direction is text-to-image:

xml
<facsimile>
  <surface xml:id="s12" lrx="3400" lry="4800">
    <graphic url="https://iiif.example.org/iiif/2/ms12/full/full/0/default.jpg"/>
    <zone xml:id="z12-l1" ulx="420" uly="690" lrx="2980" lry="780"/>
  </surface>
</facsimile>
<text><body>
  <pb facs="#s12"/>
  <l facs="#z12-l1">Incipit liber primus...</l>
</body></text>

Note the coordinate space: surface @lrx/@lry declare the image is 3400x4800 px, and every zone is measured in those same units.

Why does my zone not highlight the right region?

This is the most reported problem. Diagnose it in this order:

  1. Pixel mismatch. Your zone coordinates were captured on a downscaled preview but the viewer loads the full-resolution image. Check that surface @lrx/@lry equals the real image width/height (identify image.jpg from ImageMagick tells you), then rescale zones proportionally.
  2. Axis confusion. ulx/uly is the top-left, lrx/lry the bottom-right. A zone where lry is less than uly draws nothing.
  3. Viewer scaling. Mirador and TEI Publisher may apply their own transform; verify a single known zone before bulk-checking.

A quick rescale of every zone by a factor in XSLT:

xml
<xsl:template match="@ulx|@uly|@lrx|@lry">
  <xsl:attribute name="{name()}"><xsl:value-of select="round(. * 2.0)"/></xsl:attribute>
</xsl:template>

How do you find dangling facs pointers fast?

A pointer to a zone that does not exist silently fails — no error, just no highlight. Catch every broken pointer in one command:

bash
xmllint --noout --xpath \
  "//*[@facs][not(//@xml:id = substring-after(@facs,'#'))]" edition.xml

If that returns nodes, those elements point nowhere. For ongoing projects, encode the same logic as a Schematron rule so validation flags it automatically.

Page-level versus line-level: which should you use?

ApproachEffortBest forTrade-off
pb facs onlylowreading editions, page-flip viewersno in-text region highlight
lb/l facs zoneshighHTR alignment, line searchmany zones to capture and maintain
word-level zonevery highsearchable image text, ground truthonly worth it for training data

Most editions stop at page level. Reach for line zones only when a real feature — search-in-image or HTR ground truth — justifies the cost.

Linking to IIIF rather than local files

If your images live behind a IIIF Image API, put the request URL in graphic @url and let the surface dimensions match the info.json width/height. You can even target a region server-side with a IIIF region request (for example .../full/!1000,/0/default.jpg), which keeps your TEI light and lets the image server do the cropping. Point facsimile @source at the IIIF manifest so consuming tools can resolve canvases.

Why does facsimile fail to validate?

Three recurring schema faults:

  • facsimile placed inside text or body instead of as a sibling of text.
  • zone elements not wrapped in a surface.
  • A graphic missing its required @url.

Validate against your project ODD, not raw tei_all, so customisation-specific rules (for example, a required @type on zones) are enforced too.

Key Takeaways

  • The link runs text-to-image: the text element carries facs, pointing at a zone @xml:id.
  • Mismatched regions usually mean coordinates in the wrong pixel space — match surface @lrx/@lry to the real image size.
  • Test that every @facs resolves to an existing @xml:id; dangling pointers fail silently.
  • Use page-level pb facs by default; add line/word zones only when a feature demands it.
  • For IIIF, put the Image API URL in graphic @url and reference the manifest via @source.
  • facsimile must be a sibling of text, with zone always inside a surface.

Frequently Asked Questions

Why does my facsimile zone not highlight the right region?

Almost always the coordinates (@points or @ulx/@uly/@lrx/@lry) are in display pixels instead of the image's native resolution, or the viewer applies a different scale. Confirm the surface @lrx/@lry match the actual image dimensions, then express all zones in those same pixel units.

Give the zone an xml:id and point to it from the text with facs pointing at that id on the corresponding l, line or pb. The pointer goes text-to-image, so the text element carries facs, not the other way round.

Should I use facs on pb or on individual lines?

Use pb with facs to link a whole page, and add facs on lb/l only when you have line-level zones worth the extra effort. Page-level linking is enough for most reading editions; line-level pays off for HTR alignment and word-search-in-image.

Yes. Put the IIIF Image API URL (or a region request) in graphic @url inside the surface, or reference a IIIF canvas. Many tools also accept a @source on facsimile pointing at the IIIF manifest.

Why is my facsimile element failing validation?

Common causes are placing facsimile outside its allowed position (it must be a sibling of text inside TEI), missing the surface wrapper around zone elements, or a facs pointer that does not resolve to an existing xml:id.

How do I check that every facs pointer actually resolves?

Run an XPath or Schematron test that every @facs value (minus the hash) matches an existing @xml:id. A one-line check with xmllint --xpath or a Schematron rule catches dangling pointers before they reach the viewer.