{
if (!html) return
for (let ma; (ma = matchIcon.exec(html));) {
if (ma[1] in fontAwesomeCodes) {
this.usedIcons.add(ma[1])
} else {
console.warn('Unknown icon:', ma[1])
}
}
}
scan(this.pages.title)
this.chaptersHtml.forEach(scan)
this.notesHtml.forEach(scan)
if (this.usedIcons.size === 0) {
this.iconsFont = null
return
}
const glyphs = [...this.usedIcons].map((name) => {
return fontAwesomeCodes[name].charCodeAt(0)
})
const fontFile = require('font-awesome/fonts/fontawesome-webfont.ttf')
this.iconsFont = await subsetFont(fontFile, glyphs, { local: isNode })
}
iconsStyle () {
if (this.usedIcons.size === 0) return ''
let style = iconsCss.trim() + '\n'
this.usedIcons.forEach((name) => {
style += '.fa-' + name + ':before { content: "\\' + fontAwesomeCodes[name].charCodeAt(0).toString(16) + '"; }\n'
})
return style
}
fetchCoverImage () {
if (this.pcache.coverImage) {
return this.pcache.coverImage
}
if (this.coverImage) {
return Promise.resolve(this.coverImage)
}
this.coverImage = null
const url = this.coverUrl || this.storyInfo.full_image
if (!url) {
console.warn('Story has no image. Generating one...')
let canvas
if (isNode) {
canvas = require('canvas').createCanvas(1080, 1440)
} else {
canvas = document.createElement('canvas')
canvas.width = 1080
canvas.height = 1440
}
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = 'black'
ctx.textAlign = 'center'
ctx.strokeStyle = 'black'
ctx.strokeRect(20, 20, canvas.width - 40, canvas.height - 40)
ctx.strokeRect(12, 12, canvas.width - 24, canvas.height - 24)
const title = this.storyInfo.title
const author = this.storyInfo.author.name
let fontSize = 150
let width
do {
ctx.font = 'bold ' + fontSize + 'px sans-serif'
width = ctx.measureText(title).width
fontSize -= 5
} while (width > canvas.width * 0.85)
ctx.fillText(title, canvas.width / 2, canvas.height * 0.2)
fontSize = 75
do {
ctx.font = fontSize + 'px sans-serif'
width = ctx.measureText(author).width
fontSize -= 5
} while (width > canvas.width * 0.7)
ctx.fillText(author, canvas.width / 2, canvas.height * 0.9)
this.setCoverImage(Buffer.from(canvas.toDataURL('image/jpeg').split(',')[1], 'base64'))
return Promise.resolve(this.coverImage)
}
this.progress(0, 0, 'Fetching cover image...')
this.pcache.coverImage = fetchRemote(url, 'arraybuffer').then((data) => {
data = isNode ? data : new Uint8Array(data)
const info = fileType(data)
if (info) {
const type = info.mime
const isImage = type.startsWith('image/')
if (!isImage) {
return null
}
const filename = 'Images/cover.' + info.ext
this.coverFilename = filename
this.coverType = type
this.coverImageDimensions = sizeOf(Buffer.from(data))
this.coverImage = data
this.coverFilename = filename
return this.coverImage
} else {
return null
}
}).then((data) => {
this.pcache.coverImage = null
return data
})
return this.pcache.coverImage
}
fetchTitlePage () {
let viewMature = true
const isStoryMature = this.storyInfo.content_rating === 2
if (!isNode) {
viewMature = document.cookie.split('; ').includes('view_mature=true')
if (!viewMature && isStoryMature) {
if (window.setCookie) {
window.setCookie('view_mature', true, 365)
} else {
document.cookie = 'view_mature=true; path=/'
}
}
}
return fetch(this.storyInfo.url).then((data) => {
if (!viewMature && isStoryMature) {
// Delete cookie
document.cookie = 'view_mature=false; path=/; Max-Age=0'
}
return data
}).then(this.extractTitlePageInfo.bind(this))
}
extractTitlePageInfo (html) {
let startTagsPos = html.indexOf('') + 23
let tagsHtml = html.substring(startTagsPos)
const endTagsPos = tagsHtml.indexOf('')
tagsHtml = tagsHtml.substring(0, endTagsPos)
const tags = []
let c
tags.byImage = {}
this.subjects.length = 0
this.subjects.push('Fimfiction')
this.subjects.push(this.storyInfo.content_rating_text)
const matchTag = /
(.*?)<\/a>/g
for (;(c = matchTag.exec(tagsHtml));) {
const cat = {
url: 'https://fimfiction.net' + c[1],
className: 'story-tag ' + c[2],
name: entities.decode(c[4]),
type: c[2].replace('tag-', '')
}
tags.push(cat)
this.subjects.push(cat.name)
}
this.tags = tags
html = html.substring(endTagsPos + 5)
html = html.substring(html.indexOf('') + 38)
let ma = html.match(/This story is a sequel to (.*?)<\/a>/)
if (ma) {
this.storyInfo.prequel = {
url: 'https://fimfiction.net' + ma[1],
title: entities.decode(ma[2])
}
html = html.substring(html.indexOf('
') + 6)
}
const endDescPos = html.indexOf('\n')
const description = html.substring(0, endDescPos).trim()
this.description = description
html = html.substring(endDescPos + 7)
const extraPos = html.indexOf('\n')
chapter = chapter.substring(0, pos).trim()
// remove leading and trailing
tags and whitespace
chapter = chapter.replace(trimWhitespace, '')
return { content: chapter, notes: authorNotes, notesFirst: authorNotesPos < chapterPos }
}
replaceRemoteResources () {
if (this.remoteResources.size === 0) return
if (!this.options.includeExternal) {
this.remoteResources.forEach((r, url) => {
if (r.originalUrl && r.where) {
const ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
for (var i = 0; i < r.where.length; i++) {
const w = r.where[i]
if (typeof w === 'number') {
if (ourl.test(this.chapters[w])) {
this.storyInfo.chapters[w].remote = true
}
} else if (w === 'titlepage') {
if (ourl.test(this.pages.title)) {
this.hasRemoteResources.titlePage = true
}
}
}
}
})
} else {
this.remoteResources.forEach((r, url) => {
if (r.dest && r.originalUrl && r.where) {
const dest = '../' + r.dest
const ourl = new RegExp(escapeStringRegexp(r.originalUrl), 'g')
for (var i = 0; i < r.where.length; i++) {
const w = r.where[i]
if (typeof w === 'object' && w.chapter !== undefined && this.chaptersHtml[w.chapter]) {
this.chaptersHtml[w.chapter] = this.chaptersHtml[w.chapter].replace(ourl, dest)
} else if (typeof w === 'object' && w.note !== undefined && this.notesHtml[w.note]) {
this.notesHtml[w.note] = this.notesHtml[w.note].replace(ourl, dest)
} else if (w === 'titlepage') {
this.pages.title = this.pages.title.replace(ourl, dest)
}
}
} else {
console.log('bad remote', r, url)
}
})
}
}
}
export default FimFic2Epub