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.