74dc80fd3bd2a70371fa75218347ddc45a556dde
braney
  Wed Aug 5 07:28:59 2026 -0700
Docent: render a tour at print resolution, and make zoom wait for its redraw

refs #37892

scale: k (DOCENT_SCALE=k, make hires [SCALE=3]) renders the same tour with k
times the pixels, for figures that have to print -- a screen still is about
120 dpi across a journal column. Nothing is upscaled; each layer that draws is
asked for more:

* deviceScaleFactor: k with the viewport left at its 1x CSS size, so the page
lays out exactly as at 1x -- same line breaks, same jQuery-dialog width, same
tooltip placement -- and rasterizes with k times the pixels,
* pix x k so the server draws a wider browser image, with textSize stepped up
its ladder to match (3x lands on 24) so hgTracks makes the same layout
decisions in it: same tick spacing, same room for labels, same packing of
features into rows,
* zoom: 1/k on the image table, handing that wider image the 1x amount of
layout space, so one image pixel falls on one device pixel.

A scaled run is stills-only (no mp4), and DOCENT_STILLS names a different parent
so a print render lands beside the screen stills instead of over them.

Everything hgTracks reports about the image -- map-box coords, mouseOver spans,
insideX, a px: drag endpoint -- is in the pixels the SERVER drew, which is not
the displayed size once the image is scaled. Those now go through the image's
natural-to-displayed ratio (1 at 1x). Without it a named mouseover: lands k
times off and pins a different feature.

zoom: in|out waited only for #imgTbl, which the buttons never remove (they
redraw in place via ajax), so the following step could read the previous
window's map boxes and report an item "not found" that was simply not in view
yet. It now waits for the window itself to change. Pre-existing, hidden by the
dwell that FAST removes -- and every hires run is FAST. An item-not-found error
also names the current window and the items that ARE in that row, which is what
told the two cases apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/utils/docent/docent.mk src/hg/utils/docent/docent.mk
index dfd6ef68d86..7925bf6c2d7 100644
--- src/hg/utils/docent/docent.mk
+++ src/hg/utils/docent/docent.mk
@@ -26,31 +26,49 @@
 DOCENT  ?= $(HOME)/kent/src/hg/utils/docent/docent.js
 SCRIPTS ?= $(wildcard *.docent.yaml)
 BASES   ?= $(SCRIPTS:.docent.yaml=)
 FIGDIR  ?= ..
 MP4S    := $(addprefix $(FIGDIR)/,$(addsuffix .mp4,$(BASES)))
 
 # Shared Playwright/Chromium install. Anywhere you have playwright + js-yaml works;
 # at UCSC this is the ~/pwrec tree.
 PW_ENV  ?= PLAYWRIGHT_BROWSERS_PATH=$(HOME)/pwrec/browsers NODE_PATH=$(HOME)/pwrec/node_modules
 
 # FAST=1 -> figures only: no dwells, no cursor animation, no screen recording, no mp4.
 # Same stills, about a third of the wall clock. Use it while iterating on figure content;
 # drop it for the final build that has to produce the videos.
 FAST_ENV = $(if $(FAST),DOCENT_FAST=1 ,)
 
-.PHONY: all list clean $(BASES)
+.PHONY: all list clean hires $(BASES)
 
 all: $(MP4S)
 
 $(FIGDIR)/%.mp4: %.docent.yaml $(DOCENT)
 	$(FAST_ENV)$(PW_ENV) node $(DOCENT) $<
 
+# hires: the same tours rendered for print -- SCALE times the pixels (a wider server image
+# drawn with a bigger track font, the HTML zoomed to match), stills only, written to their
+# own tree so the screen stills and the videos are left alone. Always a full rebuild: a
+# print run is rare and cheap to ask for exactly when it is wanted.
+#
+#   make hires                     # every scenario at 3x -> stills.hires/<base>/
+#   make hires SCALE=2             # 2x
+#   make hires BASES=BP1           # one scenario
+#
+SCALE ?= 3
+HIRES ?= stills.hires
+hires:
+	@for b in $(BASES); do \
+	  echo "=== $$b at $(SCALE)x"; \
+	  DOCENT_SCALE=$(SCALE) DOCENT_STILLS=$(HIRES) DOCENT_FAST=1 $(PW_ENV) \
+	    node $(DOCENT) $$b.docent.yaml || exit 1; \
+	done
+
 # Convenience: `make AP1` -> build ../AP1.mp4
 $(BASES): %: $(FIGDIR)/%.mp4
 
 list:
 	@echo $(BASES)
 
 clean:
 	rm -f $(MP4S)
-	rm -rf stills
+	rm -rf stills $(HIRES)