Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
Add support for setting a cover page
Browse files Browse the repository at this point in the history
  • Loading branch information
bmaupin committed May 30, 2016
1 parent 6d26ff9 commit b728350
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 24 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/bmaupin/go-epub/master/LICENSE)

Work in progress.

**Features:**

- [Clean, documented API](https://godoc.org/github.com/bmaupin/go-epub)
Expand All @@ -14,5 +12,4 @@ Work in progress.

**Wishlist:**

- Add support for cover pages
- Add functionality to read EPUB files
92 changes: 79 additions & 13 deletions epub.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,54 @@ import (
var ErrFilenameAlreadyUsed = errors.New("Filename already used")

const (
cssFileFormat = "css%04d.css"
defaultEpubLang = "en"
imageFileFormat = "image%04d%s"
sectionFileFormat = "section%04d.xhtml"
urnUUIDPrefix = "urn:uuid:"
cssFileFormat = "css%04d.css"
defaultCoverBody = `<img src="%s" alt="Cover Image" />`
defaultCoverCSSContent = `body {
background-color: #FFFFFF;
margin-bottom: 0px;
margin-left: 0px;
margin-right: 0px;
margin-top: 0px;
text-align: center;
}
img {
height: 100%;
max-width: 100%;
}`
defaultCoverFilename = "cover.xhtml"
defaultEpubLang = "en"
imageFileFormat = "image%04d%s"
sectionFileFormat = "section%04d.xhtml"
urnUUIDPrefix = "urn:uuid:"
)

// Epub implements an EPUB file.
type Epub struct {
author string
css map[string]string
images map[string]string // Images added to the EPUB
lang string // Language
pkg *pkg // The package file (package.opf)
sections map[string]xhtml // Sections (chapters)
author string
// The first value is the cover xhtml filename, the second is the cover
// image filename
cover [2]string
// The key is the css filename, the value is the file content
css map[string]string
// The key is the image filename, the value is the image source
images map[string]string
// Language
lang string
// The package file (package.opf)
pkg *pkg
// The key is the section filename, the value is the section xhtml
sections map[string]xhtml
title string
toc *toc // Table of contents
uuid string
// Table of contents
toc *toc
uuid string
}

// NewEpub returns a new Epub.
func NewEpub(title string) *Epub {
e := &Epub{}
e.cover[0] = ""
e.css = make(map[string]string)
e.images = make(map[string]string)
e.sections = make(map[string]xhtml)
Expand Down Expand Up @@ -188,6 +213,47 @@ func (e *Epub) SetAuthor(author string) {
e.pkg.setAuthor(author)
}

// SetCover sets the cover page for the EPUB using the provided path to image
// file and optional path to CSS file.
//
// If the path to the CSS file isn't provided, a default cover CSS file will be
// created and used.
func (e *Epub) SetCover(imagePath string, cssPath string) {
var err error

// If a cover already exists, remove it from the EPUB
if e.cover[0] != "" {
delete(e.sections, e.cover[0])
}

// Create a default cover stylesheet if one isn't provided
if cssPath == "" {
cssPath, err = e.AddCSS(defaultCoverCSSContent, "")
if err != nil {
// This shouldn't cause an error since we're not specifying a filename
panic(fmt.Sprintf("Error adding default cover CSS file: %s", err))
}
}

coverBody := fmt.Sprintf(defaultCoverBody, imagePath)

// Title won't be used since the cover won't be added to the TOC
// First try to use the default cover filename
coverPath, err := e.AddSection("", coverBody, defaultCoverFilename, cssPath)
// If that doesn't work, generate a filename
if err != nil {
coverPath, err = e.AddSection("", coverBody, "", cssPath)
if err != nil {
// This shouldn't cause an error since we're not specifying a filename
panic(fmt.Sprintf("Error adding default cover XHTML file: %s", err))
}
}

// Set the cover field to the base filename
e.cover[0] = filepath.Base(coverPath)
e.cover[1] = filepath.Base(imagePath)
}

// SetLang sets the language of the EPUB.
func (e *Epub) SetLang(lang string) {
e.lang = lang
Expand Down
42 changes: 40 additions & 2 deletions epub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,18 @@ img {
height: 100%;
max-width: 100%;
}`
testCoverCSSFilename = "cover.css"
testCoverCSSFilename = "cover.css"
testCoverContentTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<link rel="stylesheet" type="text/css" href="%s"></link>
</head>
<body>
<img src="%s" alt="Cover Image" />
</body>
</html>`
testCSSLinkTemplate = `<link rel="stylesheet" type="text/css" href="%s"></link>`
testDirPerm = 0775
testEpubAuthor = "Hingle McCringleberry"
Expand Down Expand Up @@ -461,16 +472,43 @@ func TestEpubUUID(t *testing.T) {
cleanup(testEpubFilename, tempDir)
}

func TestSetCover(t *testing.T) {
e := NewEpub(testEpubTitle)
testImagePath, _ := e.AddImage(testImageFromFileSource, testImageFromFileFilename)
testCSSPath, _ := e.AddCSS(testCoverCSSContent, testCoverCSSFilename)
e.SetCover(testImagePath, testCSSPath)

tempDir := writeAndExtractEpub(t, e, testEpubFilename)

contents, err := ioutil.ReadFile(filepath.Join(tempDir, contentFolderName, xhtmlFolderName, defaultCoverFilename))
if err != nil {
t.Errorf("Unexpected error reading cover XHTML file: %s", err)
}

testCoverContents := fmt.Sprintf(testCoverContentTemplate, testEpubTitle, testCSSPath, testImagePath)
if trimAllSpace(string(contents)) != trimAllSpace(testCoverContents) {
t.Errorf(
"Cover file contents don't match\n"+
"Got: %s\n"+
"Expected: %s",
contents,
testCoverContents)
}

cleanup(testEpubFilename, tempDir)
}

func TestEpubValidity(t *testing.T) {
e := NewEpub(testEpubTitle)
testCSSPath, _ := e.AddCSS(testCoverCSSContent, testCoverCSSFilename)
e.AddCSS(testCoverCSSContent, "")
e.AddSection(testSectionTitle, testSectionBody, testSectionFilename, testCSSPath)
e.AddImage(testImageFromFileSource, testImageFromFileFilename)
testImagePath, _ := e.AddImage(testImageFromFileSource, testImageFromFileFilename)
e.AddImage(testImageFromURLSource, "")
e.AddSection(testSectionTitle, testSectionBody, testSectionFilename, "")
e.AddSection(testSectionTitle, testSectionBody, "", "")
e.SetAuthor(testEpubAuthor)
e.SetCover(testImagePath, testCSSPath)
e.SetLang(testEpubLang)
e.SetTitle(testEpubAuthor)
e.SetUUID(testEpubUUID)
Expand Down
4 changes: 2 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func ExampleEpub_AddImage() {
func ExampleEpub_AddSection() {
e := epub.NewEpub("My title")

// Add a section
// Add a section. The CSS path is optional
section1Content := ` <h1>Section 1</h1>
<p>This is a paragraph.</p>`
section1Path, err := e.AddSection("Section 1", section1Content, "firstsection.xhtml", "")
Expand All @@ -77,7 +77,7 @@ func ExampleEpub_AddSection() {
section2Content := fmt.Sprintf(` <h1>Section 2</h1>
<a href="%s">Link to section 1</a>`,
section1Path)
// The filename is optional
// The filename is also optional
section2Path, err := e.AddSection("Section 2", section2Content, "", "")
if err != nil {
log.Fatal(err)
Expand Down
32 changes: 28 additions & 4 deletions write.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ const (
</rootfiles>
</container>
`
contentFolderName = "EPUB"
contentFolderName = "EPUB"
coverImageProperties = "cover-image"
// Permissions for any new directories we create
dirPermissions = 0755
// Permissions for any new files we create
Expand Down Expand Up @@ -351,8 +352,14 @@ func (e *Epub) writeImages(tempDir string) error {
imageMediaType = mediaTypeSvg
}

// The cover image has a special value for the properties attribute
imageProperties := ""
if imageFilename == e.cover[1] {
imageProperties = coverImageProperties
}

// Add the image to the package file manifest
e.pkg.addToManifest(imageFilename, filepath.Join(imageFolderName, imageFilename), imageMediaType, "")
e.pkg.addToManifest(imageFilename, filepath.Join(imageFolderName, imageFilename), imageMediaType, imageProperties)
}
}

Expand Down Expand Up @@ -380,15 +387,32 @@ func (e *Epub) writePackageFile(tempDir string) {
func (e *Epub) writeSections(tempDir string) {
if len(e.sections) > 0 {
sectionIndex := 0

// If a cover was set, add it to the package spine first so it shows up
// first in the reading order
if e.cover[0] != "" {
e.pkg.addToSpine(e.cover[0])
}

for sectionFilename, section := range e.sections {
sectionIndex++

// Set the title of the cover page to the title of the EPUB
if sectionFilename == e.cover[0] {
section.setTitle(e.Title())
}

sectionFilePath := filepath.Join(tempDir, contentFolderName, xhtmlFolderName, sectionFilename)
section.write(sectionFilePath)

relativePath := filepath.Join(xhtmlFolderName, sectionFilename)
e.toc.addSection(sectionIndex, section.Title(), relativePath)
if sectionFilename != e.cover[0] {
// Don't add the cover page to the TOC
e.toc.addSection(sectionIndex, section.Title(), relativePath)
// The cover page should have already been added to the spine first
e.pkg.addToSpine(sectionFilename)
}
e.pkg.addToManifest(sectionFilename, relativePath, mediaTypeXhtml, "")
e.pkg.addToSpine(sectionFilename)
}
}
}
Expand Down

0 comments on commit b728350

Please sign in to comment.