readium-shared-js icon indicating copy to clipboard operation
readium-shared-js copied to clipboard

EPUB spec: FXL content must be clipped when meta viewport is smaller

Open danielweck opened this issue 9 years ago • 1 comments

http://www.idpf.org/epub/301/spec/epub-contentdocs.html#sec-fxl-xhtml-svg-dimensions

Reading Systems must clip XHTML content to the ICB dimensions declared in the viewport meta tag; content positioned outside of the initial containing block will not be visible.

When the ICB aspect ratio does not match the aspect ratio of the Reading System content display area, Reading Systems may position the ICB inside the area to accommodate the user interface; in other words, added letter-boxing space may appear on either side (or both) of the content.

danielweck avatar Jun 08 '16 15:06 danielweck

See _contentMetaSize in https://github.com/readium/readium-shared-js/blob/develop/js/views/fixed_view.js and: https://github.com/readium/readium-shared-js/blob/develop/js/views/one_page_view.js#L606

    function updateMetaSize() {

        _meta_size.width = 0;
        _meta_size.height = 0;

        var size = undefined;

        var isFallbackDimension = false;
        var widthPercent = undefined;
        var heightPercent = undefined;

        var contentDocument = _$iframe[0].contentDocument;

        // first try to read viewport size
        var content = $('meta[name=viewport]', contentDocument).attr("content");

        // if not found try viewbox (used for SVG)
        if (!content) {
            content = $('meta[name=viewbox]', contentDocument).attr("content");
        }

        if (content) {
            size = parseMetaSize(content);
        }

        if (!size) {

            //var $svg = $(contentDocument).find('svg');
            // if($svg.length > 0) {
            if (contentDocument && contentDocument.documentElement && contentDocument.documentElement.nodeName && contentDocument.documentElement.nodeName.toLowerCase() == "svg") {

                var width = undefined;
                var height = undefined;

                var wAttr = contentDocument.documentElement.getAttribute("width");
                var isWidthPercent = wAttr && wAttr.length >= 1 && wAttr[wAttr.length - 1] == '%';
                if (wAttr) {
                    try {
                        width = parseInt(wAttr, 10);
                    }
                    catch (err) {}
                }
                if (width && isWidthPercent) {
                    widthPercent = width;
                    width = undefined;
                }

                var hAttr = contentDocument.documentElement.getAttribute("height");
                var isHeightPercent = hAttr && hAttr.length >= 1 && hAttr[hAttr.length - 1] == '%';
                if (hAttr) {
                    try {
                        height = parseInt(hAttr, 10);
                    }
                    catch (err) {}
                }
                if (height && isHeightPercent) {
                    heightPercent = height;
                    height = undefined;
                }

                if (width && height) {
                    size = {
                        width: width,
                        height: height
                    }
                }
                else {
                    /// DISABLED (not a satisfactory fallback)
                    // content = $svg.attr('viewBox');
                    // if(content) {
                    //     size = parseViewBoxSize(content);
                    // }
                    //
                    // if (size) {
                    //     console.warn("Viewport SVG: using viewbox!");
                    // }
                }
            }
        }

        if (!size && _currentSpineItem) {
            content = _currentSpineItem.getRenditionViewport();

            if (content) {
                size = parseMetaSize(content);
                if (size) {
                    console.log("Viewport: using rendition:viewport dimensions");
                }
            }
        }

        if (!size) {
            // Image fallback (auto-generated HTML template when WebView / iFrame is fed with image media type)
            var $img = $(contentDocument).find('img');
            if ($img.length > 0) {
                size = {
                    width: $img.width(),
                    height: $img.height()
                };

                var isImage = _currentSpineItem && _currentSpineItem.media_type && _currentSpineItem.media_type.length && _currentSpineItem.media_type.indexOf("image/") == 0;
                if (!isImage) {
                    console.warn("Viewport: using img dimensions!");
                }
            }
            else {
                $img = $(contentDocument).find('image');
                if ($img.length > 0) {
                    var width = undefined;
                    var height = undefined;

                    var wAttr = $img[0].getAttribute("width");
                    if (wAttr) {
                        try {
                            width = parseInt(wAttr, 10);
                        }
                        catch (err) {}
                    }
                    var hAttr = $img[0].getAttribute("height");
                    if (hAttr) {
                        try {
                            height = parseInt(hAttr, 10);
                        }
                        catch (err) {}
                    }


                    if (width && height) {
                        size = {
                            width: width,
                            height: height
                        };

                        isFallbackDimension = true;

                        console.warn("Viewport: using image dimensions!");
                    }
                }
            }
        }

        if (!size) {
            // Not a great fallback, as it has the aspect ratio of the full window, but it is better than no display at all.
            width = _$viewport.width();
            height = _$viewport.height();

            // hacky method to determine the actual available horizontal space (half the two-page spread is a reasonable approximation, this means that whatever the size of the other iframe / one_page_view, the aspect ratio of this one exactly corresponds to half the viewport rendering surface)
            var isTwoPageSyntheticSpread = $("iframe.iframe-fixed", _$viewport).length > 1;
            if (isTwoPageSyntheticSpread) width *= 0.5;

            // the original SVG width/height might have been specified as a percentage of the containing viewport
            if (widthPercent) {
                width *= (widthPercent / 100);
            }
            if (heightPercent) {
                height *= (heightPercent / 100);
            }

            size = {
                width: width,
                height: height
            };

            isFallbackDimension = true;

            console.warn("Viewport: using browser / e-reader viewport dimensions!");
        }

        if (size) {
            _meta_size.width = size.width;
            _meta_size.height = size.height;

            // Not strictly necessary, let's preserve the percentage values
            // if (isFallbackDimension && contentDocument && contentDocument.documentElement && contentDocument.documentElement.nodeName && contentDocument.documentElement.nodeName.toLowerCase() == "svg") {
            //     contentDocument.documentElement.setAttribute("width", size.width + "px");
            //     contentDocument.documentElement.setAttribute("height", size.height + "px");
            // }
        }
    }

danielweck avatar Jun 08 '16 15:06 danielweck