HTML类书籍分页

62

如何将HTML文件的内容分割成适合屏幕大小的块,以便在WebKit浏览器中“分页”呈现?

每个“页面”应该显示完整量的文本。这意味着一行文本不能被切成屏幕顶部或底部边界的一半。

编辑

最初我将此问题标记为“Android”,因为我的目的是构建一个Android ePub阅读器。然而,似乎可以通过JavaScript和CSS来实现解决方案,因此我扩大了问题的范围,使其独立于平台。


如何将XHTML文件的内容分成屏幕大小的块,以“分页”书籍。 -- 这为什么要这样做? - CommonsWare
6
因为这是一种类似书本并被广泛使用的方式(例如:Aldiko),而且这也是一个有趣的问题。 :) - hpique
嗨,你下面的哪个解决方案对你有用?现在我也遇到了和你一样的问题 -:( - Dev.Sinto
老兄,你找到解决方案了吗?我非常需要它,请告诉我。 - Arunavh Krishnan
如何在 EPUB 阅读器中实现分页? - Ciro Santilli OurBigBook.com
10个回答

30

在Dan的回答的基础上,这是我解决这个问题的方法,之前一直让我苦恼。 (这段JavaScript适用于iOS Webkit,无法保证android的兼容性,请告诉我结果)

var desiredHeight;
var desiredWidth;
var bodyID = document.getElementsByTagName('body')[0];
totalHeight = bodyID.offsetHeight;
pageCount = Math.floor(totalHeight/desiredHeight) + 1;
bodyID.style.padding = 10; //(optional) prevents clipped letters around the edges
bodyID.style.width = desiredWidth * pageCount;
bodyID.style.height = desiredHeight;
bodyID.style.WebkitColumnCount = pageCount;

希望这可以帮到你...


10
请问你能指导我如何在Webview中实现这个功能吗?或者你能否贴出完整的代码?提前感谢。 - Kanak Vaghela

22

根据经验,即使是一个裸式的阅读器,你也需要花费很多时间。当我开始学习C#时,ePub阅读器是我接手的第一个大项目,但是ePub标准确实非常复杂。

您可以在此处找到ePub规范的最新版本: http://www.idpf.org/specs.htm 其中包括OPS(开放出版结构)、OPF(开放包装格式)和OCF(OEBPS容器格式)。

此外,如果有帮助的话,这是我开始的项目的C#源代码链接:

https://www.dropbox.com/sh/50kxcr29831t854/MDITIklW3I/ePub%20Test.zip

它还没有充分发挥作用;我已经好几个月没有使用过了,但如果我没记错的话,只需将ePub放入调试目录中,运行程序时只需键入书名的一部分(例如《Under the Dome》,只需键入“dome”),它就会显示书籍的详细信息。

我已经成功地为几本书实现了它的功能,但从谷歌图书下载的任何电子书都完全破坏了它。他们对ePub有一个完全奇怪的实现(至少对我来说是这样),与其他来源的书籍相比。

无论如何,希望其中的一些结构代码能对您有所帮助!


20

我也曾经需要编写类似的代码,我的解决方案如下(可行):

您需要将这些行应用于Webview...

    webView_.getSettings().setUseWideViewPort(true);
    webView_.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);

此外,您需要注入一些JavaScript代码。我在使用webview渲染页面时遇到了许多活动和内容比例不同的问题,因此我的解决方案不会从“外部”获取任何值。

    webView_.setWebViewClient(new WebViewClient(){

            public void onPageFinished(WebView view, String url) {
                injectJavascript();
            }

        });

[...]

    public void injectJavascript() {
        String js = "javascript:function initialize() { " +
                "var d = document.getElementsByTagName('body')[0];" +
                "var ourH = window.innerHeight; " +
                "var ourW = window.innerWidth; " + 
                "var fullH = d.offsetHeight; " +
                "var pageCount = Math.floor(fullH/ourH)+1;" +
                "var currentPage = 0; " +
                "var newW = pageCount*ourW; " +
                "d.style.height = ourH+'px';" +
                "d.style.width = newW+'px';" +
                "d.style.webkitColumnGap = '2px'; " +
                "d.style.margin = 0; " +
                "d.style.webkitColumnCount = pageCount;" +
                "}";
        webView_.loadUrl(js);
        webView_.loadUrl("javascript:initialize()");
    }

享受 :)


这个可以运行。我如何在按钮点击时滚动到下一列? - Midhun Sivaraj
@Midhun,$("body").animate({scrollLeft: window.innerWidth*<您的列索引>}); - Jmorvan
如何在滚动时使用此代码进行页面分页? - Illya Bublyk
这个功能非常好,但有一个问题。我能够向下滚动,底部有很多额外的空间。如何移除这些额外的空间? - Srikar Reddy
currentPage 变量在 js 字符串中的作用是什么? - Srikar Reddy

12

我最近尝试了类似的事情,并添加了一些CSS样式来将布局更改为水平而不是垂直。这样可以在不修改Epub内容的情况下实现所需的效果。

这段代码应该可以工作。

mWebView.setWebViewClient(new WebViewClient() {
    public void onPageFinished(WebView view, String url) {

        // Column Count is just the number of 'screens' of text. Add one for partial 'screens'
        int columnCount = Math.floor(view.getHeight() / view.getWidth())+1;

        // Must be expressed as a percentage. If not set then the WebView will not stretch to give the desired effect.
        int columnWidth = columnCount * 100;

        String js = "var d = document.getElementsByTagName('body')[0];" + 
            "d.style.WebkitColumnCount=" + columnCount + ";" + 
            "d.style.WebkitColumnWidth='" + columnWidth + "%';";
        mWebView.loadUrl("javascript:(function(){" + js + "})()");
    }
});

mWebView.loadUrl("file:///android_asset/chapter.xml");

所以,基本上你是注入JavaScript来改变章节加载后(非常重要)body元素的样式。这种方法唯一的缺点是当内容中有图像时,计算的列数就会出错。不过修复应该不难。我的尝试是注入一些JavaScript来添加宽度和高度属性到DOM中的所有没有任何属性的图像。

希望能帮到你。

-Dan


1
+1 我没能让它工作,但我认为它指引了我正确的方向。 - hpique
@Dan,你能分享一下你的CSS样式吗?分页已经可以使用了,但是我需要相应的CSS。请发布你的CSS。先谢谢了。 - Milan Sheth

5
我能够改进Nacho的解决方案,以在WebView中获得水平滑动分页效果。 您可以在这里找到解决方案和示例代码

编辑:

解决方案代码。

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        WebView.setWebContentsDebuggingEnabled(true);
    }
    wv = (HorizontalWebView) findViewById(R.id.web_view);
    wv.getSettings().setJavaScriptEnabled(true);
    wv.setWebViewClient(new WebViewClient() {

        public void onPageFinished(WebView view, String url) {
            injectJavascript();
        }
    });

    wv.setWebChromeClient(new WebChromeClient() {
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            int pageCount = Integer.parseInt(message);
            wv.setPageCount(pageCount);
            result.confirm();
            return true;
        }
    });
    wv.loadUrl("file:///android_asset/ch03.html");   // now it will not fail here
}

private void injectJavascript() {
    String js = "function initialize(){\n" +
            "    var d = document.getElementsByTagName('body')[0];\n" +
            "    var ourH = window.innerHeight;\n" +
            "    var ourW = window.innerWidth;\n" +
            "    var fullH = d.offsetHeight;\n" +
            "    var pageCount = Math.floor(fullH/ourH)+1;\n" +
            "    var currentPage = 0;\n" +
            "    var newW = pageCount*ourW;\n" +
            "    d.style.height = ourH+'px';\n" +
            "    d.style.width = newW+'px';\n" +
            "    d.style.margin = 0;\n" +
            "    d.style.webkitColumnCount = pageCount;\n" +
            "    return pageCount;\n" +
            "}";
    wv.loadUrl("javascript:" + js);
    wv.loadUrl("javascript:alert(initialize())");
}

在我的WebChromeClient的onJsAlert中获取水平页面的数量,我将其传递给自定义的HorizontalWebView以实现分页效果。

HorizontalWebView.java

public class HorizontalWebView extends WebView {
    private float x1 = -1;
    private int pageCount = 0;

    public HorizontalWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x1 = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                float x2 = event.getX();
                float deltaX = x2 - x1;
                if (Math.abs(deltaX) > 100) {
                    // Left to Right swipe action
                    if (x2 > x1) {
                        turnPageLeft();
                        return true;
                    }

                    // Right to left swipe action
                    else {
                        turnPageRight();
                        return true;
                    }

                }
                break;
        }
        return true;
    }

    private int current_x = 0;

    private void turnPageLeft() {
        if (getCurrentPage() > 0) {
            int scrollX = getPrevPagePosition();
            loadAnimation(scrollX);
            current_x = scrollX;
            scrollTo(scrollX, 0);
        }
    }

    private int getPrevPagePosition() {
        int prevPage = getCurrentPage() - 1;
        return (int) Math.ceil(prevPage * this.getMeasuredWidth());
    }

    private void turnPageRight() {
        if (getCurrentPage() < pageCount - 1) {
            int scrollX = getNextPagePosition();
            loadAnimation(scrollX);
            current_x = scrollX;
            scrollTo(scrollX, 0);
        }
    }

    private void loadAnimation(int scrollX) {
        ObjectAnimator anim = ObjectAnimator.ofInt(this, "scrollX",
                current_x, scrollX);
        anim.setDuration(500);
        anim.setInterpolator(new LinearInterpolator());
        anim.start();
    }

    private int getNextPagePosition() {
        int nextPage = getCurrentPage() + 1;
        return (int) Math.ceil(nextPage * this.getMeasuredWidth());
    }

    public int getCurrentPage() {
        return (int) (Math.ceil((double) getScrollX() / this.getMeasuredWidth()));
    }

    public void setPageCount(int pageCount) {
        this.pageCount = pageCount;
    }
}

4
也许使用XSL-FO会起作用。这似乎对移动设备来说过于沉重,可能有些过头,但它应该能够工作,而且您不必自己实现良好分页的复杂性(例如,如何确保每个屏幕不会把文字分成两半)。
基本思路是:
  • 使用XSLT将XHTML(和其他EPUB内容)转换为XSL-FO。
  • 使用XSL-FO处理器将XSL-FO渲染成可以在移动设备上显示的分页格式,例如PDF(可以显示吗?)
我不知道Android是否有可用的XSL-FO处理器。您可以尝试使用Apache FOP。RenderX(XSL-FO处理器)具有paged-HTML输出选项的优势,但同样我不知道它是否能在Android上运行。

1

最近我也遇到了同样的问题,受到答案的启发,我使用CSS3的column-*属性找到了一个简单的CSS解决方案:

/* CSS */
.chapter {
    width: 600px;
    padding: 60px 10px;
    -webkit-column-gap: 40px;
    -webkit-column-width: 150px;
    -webkit-column-count: 2;
    height:400px;
}

/* HTML */
<div class="chapter">
    your long lorem ipsum arbitrary HTML
</div>

上面的例子在Retina iPhone上效果很好。尝试不同的属性可以得到不同的页面间距等效果。
如果需要支持多个章节,比如需要从新页面开始,可以参考Github上的XCode 5示例:https://github.com/dmrschmidt/ios_uiwebview_pagination

1

有几种方法可以实现这个功能。如果每行都在自己的元素中,您只需要检查其中一个边缘是否超出了视图(浏览器或“书页”)。

如果您想事先知道将有多少“页面”,只需将它们暂时移入视图并获取页面结束的行。这可能会很慢,因为浏览器需要重新布局页面才能知道任何内容的位置。

否则,我认为您可以使用HTML5画布元素来测量文本和/或绘制文本。

这里有一些相关信息: https://developer.mozilla.org/en/Drawing_text_using_a_canvas http://uupaa-js-spinoff.googlecode.com/svn/trunk/uupaa-excanvas.js/demo/8_2_canvas_measureText.html


0

0
你可以将页面拆分为单独的XHTML文件并将它们存储在一个文件夹中。例如:page01,page02。然后,您可以逐个呈现这些页面,并将它们放置在彼此下方。

我怎样才能知道根据屏幕和字体大小在哪里分割? - hpique

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接