/******************************************************************************* * Copyright (c) 2009, Adobe Systems Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * · Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * · Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * · Neither the name of Adobe Systems Incorporated nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ package com.adobe.dp.epub.opf; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.Vector; import com.adobe.dp.epub.io.DataSource; import com.adobe.dp.epub.ncx.TOCEntry; import com.adobe.dp.epub.ops.Element; import com.adobe.dp.epub.ops.XRef; import com.adobe.dp.epub.util.Translit; import com.adobe.dp.xml.util.SMapImpl; import com.adobe.dp.xml.util.StringUtil; import com.adobe.dp.xml.util.XMLSerializer; public class NCXResource extends Resource { TOCEntry rootTOCEntry = new TOCEntry("", null); int entryCount; Vector pages = new Vector(); private static final String ncxns = "http://www.daisy.org/z3986/2005/ncx/"; static class Page { String name; XRef xref; Page(String name, XRef xref) { this.name = name; this.xref = xref; } } NCXResource(Publication epub, String name) { super(epub, name, "application/x-dtbncx+xml", null); } public TOCEntry getRootTOCEntry() { return rootTOCEntry; } public TOCEntry createTOCEntry(String title, XRef xref) { return new TOCEntry(title, xref); } private static String increment(String pageName) { int len = pageName.length(); int numberStart = len - 1; char c; do { c = pageName.charAt(numberStart); if ('0' > c || c > '9') break; numberStart--; } while (numberStart >= 0); numberStart++; if (numberStart != len) { // ends with number int n = Integer.parseInt(pageName.substring(numberStart)); return pageName.substring(0, numberStart) + (n + 1); } // hmm, see if it is lowercase roman numeral int n = StringUtil.parseRoman(pageName); if (n > 0) return StringUtil.printRoman(n + 1); return null; // don't know what it is } public void addPage(String name, XRef location) { if (name == null || name.length() == 0) { if (pages.size() > 0) { Page lastPage = (Page) pages.lastElement(); String lastName = lastPage.name; name = increment(lastName); } if (name == null || name.length() == 0) name = Integer.toString(pages.size() + 1); } else if (name.equals(".")) name = ""; location.addUsage(XRef.USAGE_PAGE); pages.add(new Page(name, location)); } public void serialize(OutputStream out) throws IOException { XMLSerializer ser = new XMLSerializer(out); ser.startDocument("1.0", "UTF-8"); SMapImpl attrs = new SMapImpl(); attrs.put(null, "version", "2005-1"); String lang = epub.getDCMetadata("language"); if (lang != null) attrs.put(null, "xml:lang", lang); ser.startElement(ncxns, "ncx", attrs, true); ser.newLine(); ser.startElement(ncxns, "head", null, false); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "name", "dtb:uid"); String uid = epub.getDCMetadata("identifier"); if (uid == null) uid = ""; attrs.put(null, "content", uid); ser.startElement(ncxns, "meta", attrs, false); ser.endElement(ncxns, "meta"); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "name", "dtb:depth"); int depth = calculateDepth(rootTOCEntry); attrs.put(null, "content", Integer.toString(depth)); ser.startElement(ncxns, "meta", attrs, false); ser.endElement(ncxns, "meta"); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "name", "dtb:totalPageNumber"); attrs.put(null, "content", Integer.toString(pages.size())); ser.startElement(ncxns, "meta", attrs, false); ser.endElement(ncxns, "meta"); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "name", "dtb:maxPageNumber"); attrs.put(null, "content", Integer.toString(pages.size())); ser.startElement(ncxns, "meta", attrs, false); ser.endElement(ncxns, "meta"); ser.newLine(); ser.endElement(ncxns, "head"); ser.newLine(); ser.startElement(ncxns, "docTitle", null, false); ser.startElement(ncxns, "text", null, false); String title = epub.getDCMetadata("title"); if (title == null) title = ""; if (epub.isTranslit()) title = Translit.translit(title); char[] arr = title.toCharArray(); ser.text(arr, 0, arr.length); ser.endElement(ncxns, "text"); ser.endElement(ncxns, "docTitle"); ser.newLine(); ser.startElement(ncxns, "navMap", null, false); ser.newLine(); entryCount = 1; serializeChildEntries(rootTOCEntry, ser); ser.endElement(ncxns, "navMap"); ser.newLine(); if (pages.size() > 0) { ser.startElement(ncxns, "pageList", null, false); ser.startElement(ncxns, "navLabel", null, false); ser.startElement(ncxns, "text", null, false); ser.endElement(ncxns, "text"); ser.endElement(ncxns, "navLabel"); ser.newLine(); Iterator pages = this.pages.iterator(); int count = 1; while (pages.hasNext()) { Page page = (Page) pages.next(); attrs = new SMapImpl(); String countStr = Integer.toString(count++); attrs.put(null, "value", countStr); attrs.put(null, "type", "normal"); attrs.put(null, "playOrder", Integer.toString(page.xref.getPlayOrder())); ser.startElement(ncxns, "pageTarget", attrs, false); ser.newLine(); ser.startElement(ncxns, "navLabel", null, false); ser.startElement(ncxns, "text", null, false); char[] text = page.name.toCharArray(); ser.text(text, 0, text.length); ser.endElement(ncxns, "text"); ser.endElement(ncxns, "navLabel"); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "src", page.xref.makeReference(this)); ser.startElement(ncxns, "content", attrs, false); ser.endElement(ncxns, "content"); ser.newLine(); ser.endElement(ncxns, "pageTarget"); ser.newLine(); } ser.endElement(ncxns, "pageList"); } ser.endElement(ncxns, "ncx"); ser.newLine(); ser.endDocument(); } private static int calculateDepth(TOCEntry entry) { Iterator it = entry.content(); int maxDepth = 0; while (it.hasNext()) { TOCEntry child = (TOCEntry) it.next(); int depth = calculateDepth(child); if (depth > maxDepth) maxDepth = depth; } return maxDepth + 1; } private void serializeChildEntries(TOCEntry entry, XMLSerializer ser) { Iterator it = entry.content(); while (it.hasNext()) { TOCEntry child = (TOCEntry) it.next(); SMapImpl attrs = new SMapImpl(); attrs.put(null, "playOrder", Integer.toString(child.getXRef().getPlayOrder())); attrs.put(null, "id", "id" + entryCount); entryCount++; ser.startElement(ncxns, "navPoint", attrs, false); ser.newLine(); ser.startElement(ncxns, "navLabel", null, false); ser.newLine(); ser.startElement(ncxns, "text", null, false); String title = child.getTitle(); if (title == null) title = ""; if (epub.isTranslit()) title = Translit.translit(title); char[] arr = title.toCharArray(); ser.text(arr, 0, arr.length); ser.endElement(ncxns, "text"); ser.newLine(); ser.endElement(ncxns, "navLabel"); ser.newLine(); attrs = new SMapImpl(); attrs.put(null, "src", child.getXRef().makeReference(this)); ser.startElement(ncxns, "content", attrs, false); ser.endElement(ncxns, "content"); ser.newLine(); serializeChildEntries(child, ser); ser.endElement(ncxns, "navPoint"); ser.newLine(); } } void serializePageMap(PageMapResource pageMap, OutputStream out) throws IOException { final String pagemapns = "http://www.idpf.org/2007/opf"; XMLSerializer ser = new XMLSerializer(out); ser.startDocument("1.0", "UTF-8"); ser.startElement(pagemapns, "page-map", null, true); ser.newLine(); Iterator pages = this.pages.iterator(); while (pages.hasNext()) { Page page = (Page) pages.next(); SMapImpl attrs = new SMapImpl(); attrs.put(null, "name", page.name); attrs.put(null, "href", page.xref.makeReference(pageMap)); ser.startElement(pagemapns, "page", attrs, false); ser.endElement(pagemapns, "page"); ser.newLine(); } ser.endElement(pagemapns, "page-map"); ser.endDocument(); } public void prepareTOC() { /* * Adobe renderer has a bug that if a page map is used, each chapter * must start on page boundary. This code works around for this bug by * moving the first page break in the chapter to the beginning of the * chapter. Also, mark all xrefs in page map as requiring playOrder */ Iterator pages = this.pages.iterator(); OPSResource lastResource = null; boolean report = false; while (pages.hasNext()) { Page page = (Page) pages.next(); OPSResource resource = page.xref.getTargetResource(); if (resource == lastResource) continue; // chapter change: move back to the chapter boundary lastResource = resource; if (page.xref.getTargetId() != null) { if (report) { Element p = resource.getDocument().getBody(); Element t = page.xref.getTagetElement(); while (p != t) { Iterator it = p.content(); if (!it.hasNext()) break; Object f = it.next(); if (f instanceof Element) p = (Element) f; else break; } if (p != t) System.out.println("chapter break is moved"); else System.out.println("chapter break is adjusted"); } page.xref = resource.getDocument().getRootXRef(); } else if (report) { System.out.println("chapter break is good as is"); } page.xref.requestPlayOrder(); } /* * Mark all xrefs in TOC as requiring playOrder */ rootTOCEntry.requestPlayOrder(); } public void load(DataSource data) throws IOException { } }