Appearance
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:
- Pixel mismatch. Your zone coordinates were captured on a downscaled preview but the viewer loads the full-resolution image. Check that
surface @lrx/@lryequals the real image width/height (identify image.jpgfrom ImageMagick tells you), then rescale zones proportionally. - Axis confusion.
ulx/ulyis the top-left,lrx/lrythe bottom-right. A zone wherelryis less thanulydraws nothing. - 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.xmlIf 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?
| Approach | Effort | Best for | Trade-off |
|---|---|---|---|
pb facs only | low | reading editions, page-flip viewers | no in-text region highlight |
lb/l facs zones | high | HTR alignment, line search | many zones to capture and maintain |
word-level zone | very high | searchable image text, ground truth | only 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:
facsimileplaced insidetextorbodyinstead of as a sibling oftext.zoneelements not wrapped in asurface.- A
graphicmissing 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 azone @xml:id. - Mismatched regions usually mean coordinates in the wrong pixel space — match
surface @lrx/@lryto the real image size. - Test that every
@facsresolves to an existing@xml:id; dangling pointers fail silently. - Use page-level
pbfacsby default; add line/word zones only when a feature demands it. - For IIIF, put the Image API URL in
graphic @urland reference the manifest via@source. facsimilemust be a sibling oftext, withzonealways inside asurface.
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.
How do I link a transcribed line to its image region?
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.
Can I link TEI to a IIIF image instead of a local file?
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.