feature: initial implementation of EPUB generation

This commit is contained in:
Neetpone 2024-04-13 20:43:09 -04:00
parent 9ced0594e1
commit 3d7f2a51ce
8 changed files with 164 additions and 22 deletions

View file

@ -26,6 +26,7 @@ gem 'puma', '~> 5.0'
gem 'sidekiq'
# Other stuff
gem 'gepub'
gem 'marcel'
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem

View file

@ -120,6 +120,9 @@ GEM
faraday-net_http (>= 2.0, < 3.2)
faraday-net_http (3.1.0)
net-http
gepub (1.0.15)
nokogiri (>= 1.8.2, < 2.0)
rubyzip (> 1.1.1, < 2.4)
globalid (1.2.1)
activesupport (>= 6.1)
hashie (5.0.0)
@ -305,6 +308,7 @@ DEPENDENCIES
debug
elasticsearch-model
fancy_searchable!
gepub
kaminari
marcel
model-msearch

View file

@ -6,27 +6,6 @@ class ChaptersController < ApplicationController
@story = Story.find(params[:story_id])
@chapter = @story.chapters.find_by(number: params[:id])
@rendered_html = render_story
end
private
def render_story
body = @chapter.body
body.lstrip!
body = body.split "\n"
# This is fucking bad, this gets rid of the redundant title - this should be fixed upstairs,
# in the actual generation of the Markdown.
if body.length >= 2 && body[0] == @chapter.title && !body[1].empty? && body[1][0] == '='
body = body[2..]
end
markdown.render body.join("\n")
end
def markdown
@@markdown ||=
Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
@rendered_html = StoryRenderer.render_chapter(@chapter)
end
end

View file

@ -0,0 +1,43 @@
require 'gepub'
TEMPLATE_DIRECTORY = Rails.root.join('app/lib/ebook')
class Ebook::EpubGenerator
include ActionView::Helpers::TagHelper
def initialize(story)
@story = story
end
def generate
book = GEPUB::Book.new
book.add_title @story.title, title_type: GEPUB::TITLE_TYPE::MAIN, lang: :en, display_seq: 1
book.add_creator @story.author.name
book.add_item 'styles.css', content: TEMPLATE_DIRECTORY.join('files/styles.css').open
book.ordered do
book.add_item('CoverPage.html', content: generate_cover_page).landmark(type: 'cover', title: 'Cover Page')
@story.chapters.each do |chapter|
book.add_item("Chapter#{chapter.number}.html", content: generate_chapter(chapter))
.toc_text("Chapter #{chapter.number} - #{chapter.title}")
.landmark(type: 'bodymatter', title: chapter.title)
end
end
book
end
#private
def render_template(name, context)
Slim::Template.new(TEMPLATE_DIRECTORY.join("templates/#{name}.html.slim")).render(context)
end
def generate_cover_page
StringIO.new render_template('cover', @story)
end
def generate_chapter(chapter)
StringIO.new render_template('chapter', chapter)
end
end

View file

@ -0,0 +1,72 @@
.double {
margin-top: 1em;
}
.indented {
text-indent: 3em;
}
.i {
font-style: italic;
}
.u {
text-decoration: underline;
}
.b {
font-weight: bold;
}
.c {
text-align: center;
}
.s {
text-decoration: line-through;
}
img.emoticon {
margin: 0;
padding: 0;
border: 0;
height: 1em;
}
hr {
margin-top: 12px;
}
blockquote {
padding: 5px 10px;
margin: 10px;
border-left: 5px solid #aaa;
background: #eee;
}
.authors-note,
.preface {
border-top: 3px double #333;
border-bottom: 3px double #333;
}
.afterward {
border-top: 3px double #333;
}
h1 {
font-size: 1.9em;
margin: 0.8em 0;
}
h2 {
font-size: 1.3em;
margin: 0.5em 0;
}
h3 {
font-size: 0.625em;
margin: 0;
}
.authors-note,
.preface,
.afterward {
background: none;
border-left: none;
padding: 4px 0;
}
.authors-note h1,
.authors-note h2,
.preface h1,
.preface h2,
.afterward h1,
.afterward h2 {
margin: 0.5em 0;
font-size: 1.3em;
}

View file

@ -0,0 +1,10 @@
doctype xml
html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
head
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
link rel="stylesheet" href="styles.css"
title= title
body
h1= title
.calibre2#content
== StoryRenderer.render_chapter(self)

View file

@ -0,0 +1,33 @@
doctype xml
html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
head
meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
link rel="stylesheet" href="styles.css"
title Cover
body
table style="width: 100%; height: 100%;"
tr
td
h1= title
p
b by #{author.name}
hr
p
| Rating: #{content_rating}
br
| Tags: #{tags.pluck(:name).join(', ')}
br
| Length: #{num_words}
hr
table.desc
tr style="height: 100%;" valign="top"
td
p.double= short_description
== description_html
tr.bottom
td
hr
p
| Published #{date_published}
br
| Last updated #{date_updated}

BIN
test.epub Normal file

Binary file not shown.