Tuesday, February 21, 2017

Giving Life to AsciiDoc Images: Highlight Image Areas on List Hover

Though AsciiDoc (AD) has become a popular documentation medium these days, most AD-based websites still remain traditional HTML (except for, perhaps, some Bootstrap/jQuery styling). While this may be due to the need to retain the build-agnostic structure of AD sources (e.g. the same AD source can generate a fairly well-formatted website as well as a well-organized PDF), sometimes it does not hurt to utilize some low-impact tricks to produce some icing, say, for web-hostable HTML output.

For example, the following trick can be used to generate HTML output with 'highlightable' images where specific areas of images get highlighted (with a coloured frame overlay) when hovering over some corresponding text elements (e.g. items in a list of instructions/descriptions associated with the image).

  1. Designate each image:: element with an ID having a known prefix (e.g. hover-).
  2. [hover-id]
    image::sample-image.png
    
  3. For the list of elements associated with each corresponding image, add an ID attribute before the list (as the list's ID), containing the coordinates to be highlighted when hovering over each item, in the following format:
  4. [hover-id;5,5,20,20;40,40,20,20;0,90,100,10]
    * top-left corner element
    * mid element
    * bottom bar element
    

    where hover-id is the ID of the image which should be highlighted on hover, and the next three semicolon-delimited fragments denote the highlight region in percentages of width and height, for each of the three list elements, in the format top,left,width,height; e.g. if sample-image.png is 200px by 100px, 5,5,20,20 indicates a highlight region starting at (5%, 5%) or (10px, 5px) on the image area, 20%×20% (20px×10px) in size.

  5. In order to inject the necessary highlight logic into the generated HTML, include the following JS snippet in the HTML source:
  6. var links = document.querySelectorAll("*[id^=hover]");	// the ID prefix you used earlier
    foreach(links, function(link) {
    	// create svg overlay for target image
    	var parts = link.id.split(/;\s+/);
    	var imgWrapper = document.getElementById(parts[1]).children[0];
    	var img = imgWrapper.children[0];
    	var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    	svg.setAttribute("id", parts[1] + "-overlay");
    	svg.setAttribute("style", "position: absolute");
    	imgWrapper.insertBefore(svg, img);
    
    	// resize svg to match img
    	setSize(svg, img);
    	window.addEventListener("resize", function() {
    		setSize(svg, img);
    	});
    
    	// register highlighting events for each entry
    	foreach(link.children[0].children, function(item, i) {
    		item.onmouseenter = function () {
    			drawRect(svg, parts[i + 2].split(","));
    		}
    	});
    });
    
    function drawRect(svg, dim) {
    	cleanup(svg);
    	var pWidth = svg.getAttribute("width") / 100;
    	var pHeight = svg.getAttribute("height") / 100
    	var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    	rect.setAttribute("x", dim[0] * pWidth);
    	rect.setAttribute("y", dim[1] * pHeight);
    	rect.setAttribute("width", dim[2] * pWidth);
    	rect.setAttribute("height", dim[3] * pHeight);
    	rect.setAttribute("stroke", "red");
    	rect.setAttribute("fill-opacity", "0");	// transparent
    	svg.appendChild(rect);
    }
    
    function setSize(svg, img) {
    	cleanup(svg);
    	svg.setAttribute("width", img.width);
    	svg.setAttribute("height", img.height);
    }
    
    function cleanup(svg) {
    	while (svg.firstChild) {
    		svg.removeChild(svg.firstChild);
    	}
    }
    
    function foreach(list, fn) {
    	for (var i = 0; i < list.length; i++) {
    		fn(list[i], i);
    	}
    }
    

    For example, if you use AsciiDoctor to generate HTML, this can be done by saving the JS snippet into a file overlay.js and including the tag

    <script src="overlay.js" type="text/javascript"/>

    in a docinfo-footer.html which would be automatically added to the AsciiDoctor build output. Since the HTML would not be utilized in, say, a PDF build, the additional stuff won't cause any major problems.

Disclaimer: This is a very crude solution that has been tried out and tested in only a handful of pages on Firefox and Chrome; some major improvements and fixes may still be on the way!

No comments: