ti-enxame.com

Paginação HTML semelhante a um livro

Como posso dividir o conteúdo de um arquivo HTML em partes do tamanho da tela para "paginar" em um navegador WebKit? 

Cada "página" deve mostrar uma quantidade completa de texto. Isso significa que uma linha de texto não deve ser cortada pela metade na borda superior ou inferior da tela.

Editar

Esta pergunta foi originalmente marcada como "Android", pois minha intenção é criar um leitor Android ePub. No entanto, parece que a solução pode ser implementada apenas com JavaScript e CSS, então ampliei o escopo da pergunta para torná-la independente de plataforma.

59
hpique

Aproveitando a resposta de Dan, aqui está a minha solução para este problema, com o qual eu estava lutando até agora. (este JS funciona em iOS Webkit, sem garantias para o Android, mas por favor me avise os resultados)

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;

Espero que isto ajude...

30
Engin Kurutepe

Falando da experiência, espere colocar muito tempo nisso, mesmo para um espectador barebones. Um leitor ePub foi na verdade o primeiro grande projeto que assumi quando comecei a aprender C #, mas o padrão ePub é definitivamente bastante complexo.

Você pode encontrar a última versão da especificação do ePub aqui: http://www.idpf.org/specs.htm Que inclui o OPS (Open Publication Structure), OPF (Open Packaging Format) e OCF (OEBPS Container Format).

Além disso, se isso ajudar você, aqui está um link para o código-fonte C # do projeto que iniciei:

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

Não é feito em tudo; Eu não tenho jogado com isso há meses, mas se bem me lembro, basta colocar um ePub no diretório de depuração, e quando você executar o programa apenas digite alguma parte do nome (por exemplo, Under the Dome, basta digitar "dome") e exibirá os detalhes do livro. 

Eu trabalhei corretamente para alguns livros, mas todos os eBooks do Google Livros quebraram completamente. Eles têm uma implementação completamente bizarra do ePub (para mim, pelo menos) em comparação com livros de outras fontes. 

De qualquer forma, esperamos que algum código estrutural possa ajudá-lo!

21
kcoppock

Eu tive que codificar algo assim também, e minha solução (de trabalho) é esta:

Você tem que aplicar essas linhas à webview ...

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

Além disso, você tem que injetar algum javascript. Eu tive muitos problemas com as diferentes escalas da minha atividade e o conteúdo renderizado na webview, então minha solução não recebe nenhum tipo de valor de "fora".

    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()");
    }

Apreciar :)

18
Nacho L.

Recentemente, tentei algo semelhante a isso e adicionei alguns estilos CSS para alterar o layout para horizontal em vez de vertical. Isso me deu o efeito desejado sem ter que modificar o conteúdo do Epub de forma alguma.

Este código deveria trabalho.

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");

Então, basicamente você está injetando JavaScript para mudar o estilo do elemento body depois que o capítulo foi carregado (muito importante). A única queda para essa abordagem é quando você tem imagens no conteúdo que a contagem calculada da coluna fica torta. Não deve ser muito difícil de consertar embora. Minha tentativa seria injetar um pouco de JavaScript para adicionar atributos de largura e altura a todas as imagens no DOM que não possuem nenhum.

Espero que ajude.

-Dan

12
Dan

Talvez funcione usar XSL-FO . Isso parece pesado para um dispositivo móvel, e talvez seja um exagero, mas deve funcionar, e você não precisaria implementar as complexidades da boa paginação (por exemplo, como você se certifica de que cada tela não corta o texto ao meio)? .

A ideia básica seria:

  • transforme o XHTML (e outras coisas do EPUB) em XSL-FO usando XSLT.
  • use um processador XSL-FO para renderizar o XSL-FO em um formato paginado que você pode exibir no dispositivo móvel, como PDF (você pode exibir isso?)

Eu não sei se existe um processador XSL-FO disponível para Android. Você poderia tentar o Apache FOP. O RenderX (processador XSL-FO) tem a vantagem de ter uma opção de saída paged-HTML , mas novamente não sei se ele pode ser executado no Android.

4
LarsH

Consegui melhorar Nacho's solution para obter efeito de paginação de furto horizontal com o WebView. Você pode encontrar a solução aqui e example code .

Editar:

Código da solução.

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())");
}

No onJsAlert do meu WebChromeClient obtém o número de páginas horizontais que eu passo para o HorizontalWebView customizado para implementar o efeito de paginação. 

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;
    }
}
1
Gautam Chibde

Teve esse mesmo problema recentemente e inspirado pelas respostas encontradas uma solução CSS simples usando os atributos column- * do CSS3:

/* 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>

O exemplo acima dá ótimos resultados em um iPhone de retina. Brincando com os diferentes atributos resulta em espaçamento diferente entre as páginas e tal.

Se você precisar dar suporte a vários capítulos, por exemplo, que precisam começar em novas páginas, há um exemplo do XCode 5 no github: https://github.com/dmrschmidt/ios_uiwebview_pagination

1
Dennis

Existem várias maneiras de fazer isso. Se cada linha estiver em seu próprio elemento, tudo o que você precisa fazer é verificar se uma de suas bordas está fora da visualização (os navegadores ou a "página do livro").

Se você quiser saber quantas "páginas" haverá com antecedência, basta movê-las temporariamente para a visualização e obter a linha que uma página termina. Isso pode ser lento porque o reflow da página é necessário para que o navegador saiba onde está tudo.

Caso contrário, acho que você poderia usar o elemento de tela HTML5 para medir texto e/ou desenhar texto.

Algumas informações sobre isso aqui: https://developer.mozilla.org/en/Drawing_text_using_a_canvashttp://uupaa-js-spinoff.googlecode.com/svn /trunk/uupaa-excanvas.js/demo/8_2_canvas_measureText.html

1
Frank

Você pode consultar http://www.litres.ru/static/OR/or.html?data=/static/trials/00/42/47/00424722.gur.html&art=424722&user=0&trial=1 mas o código pode ser fortemente ofuscado, portanto, basta usar o Firebug para inspecionar o DOM.

Se o link não estiver funcionando, o comentário será fixo.

0
kagali-san

Você pode dividir as páginas em arquivos XHTML separados e armazená-los em uma pasta. Por exemplo: page01, page02. Você pode, então, renderizar essas páginas uma a uma por baixo da outra.

0
Charles