Compare commits

...

23 Commits

Author SHA1 Message Date
Book Pauk
ef12a84285 Merge branch 'release/0.9.12' 2020-12-19 03:23:27 +07:00
Book Pauk
6a18ae3f27 Версия 0.9.12 2020-12-19 03:22:47 +07:00
Book Pauk
a250e95950 Поправил баг 2020-12-19 03:07:59 +07:00
Book Pauk
b174ae452b Оптимизации проверок типа файла 2020-12-19 03:05:58 +07:00
Book Pauk
0b63bce357 Исправления багов 2020-12-19 02:47:06 +07:00
Book Pauk
de0d10e792 Мелкая поправка 2020-12-19 02:46:46 +07:00
Book Pauk
b358b340b4 Улучшение формирования оглавления 2020-12-19 00:20:11 +07:00
Book Pauk
455aba7f4f Мелкая поправка текста 2020-12-19 00:17:36 +07:00
Book Pauk
fde0437157 Добавлено извлечение схемы документа в ConvertPdfImages, мелкий рефакторинг 2020-12-18 23:56:55 +07:00
Book Pauk
480c95bd63 Добавлена возможность конвертирования pdf как набор изображений.
Добавлены соответствующие настройки в читалку.
2020-12-18 23:30:13 +07:00
Book Pauk
972f957685 Работа над вкладкой "Конвертирование" 2020-12-18 22:44:20 +07:00
Book Pauk
40ff04e5dc Работа над вкладкой "Конвертирование" 2020-12-18 21:48:08 +07:00
Book Pauk
b3c028bd7a Убрал устаревшее 2020-12-18 21:23:02 +07:00
Book Pauk
51ec6a54fa Переименования, небольшое улучшение html-title 2020-12-17 23:39:45 +07:00
Book Pauk
7a29b16ee8 Коментарии к 0.9.12 2020-12-17 23:37:00 +07:00
Book Pauk
7af6fd8248 Новая вкладка 2020-12-17 23:36:30 +07:00
Book Pauk
e1c93169b5 Добавлена вкладка "Конвертирование" 2020-12-17 23:35:56 +07:00
Book Pauk
f4716d5a1e Поправлен баг 2020-12-17 23:12:36 +07:00
Book Pauk
f5c06ce420 Добавлен парсинг оглавления из djvu, добавлено отображение атрибута alt изображений в ContentsPage 2020-12-17 20:57:29 +07:00
Book Pauk
9492f85d80 Merge tag '0.9.11-4' into develop
0.9.11-4
2020-12-16 21:17:05 +07:00
Book Pauk
b1303a3ba2 Merge branch 'release/0.9.11-4' 2020-12-16 21:16:58 +07:00
Book Pauk
5c9cfe5e6f Оптимизация 2020-12-16 21:15:45 +07:00
Book Pauk
b89b5322b8 Merge tag '0.9.11-3' into develop
0.9.11-3
2020-12-16 21:07:39 +07:00
21 changed files with 401 additions and 150 deletions

View File

@@ -67,6 +67,7 @@
<img v-show="imageLoaded[item.id]" class="image-thumb" :src="imageSrc[item.id]"/>
</div>
<div class="no-expand-button column justify-center items-center">
<div class="image-num">{{ item.num }}</div>
<div v-show="item.type == 'image/jpeg'" class="image-type it-jpg-color row justify-center">JPG</div>
<div v-show="item.type == 'image/png'" class="image-type it-png-color row justify-center">PNG</div>
<div v-show="!item.local" class="image-type it-net-color row justify-center">INET</div>
@@ -103,7 +104,8 @@ import * as utils from '../../../share/utils';
const ContentsPageProps = Vue.extend({
props: {
bookPos: Number
bookPos: Number,
isVisible: Boolean,
}
});
@@ -112,8 +114,8 @@ export default @Component({
Window,
},
watch: {
bookPos: function(newValue) {
this.updateBookPosSelection(newValue);
bookPos: function() {
this.updateBookPosSelection();
}
},
})
@@ -134,6 +136,7 @@ class ContentsPage extends ContentsPageProps {
//проверим, надо ли обновлять списки
if (this.parsed == parsed) {
this.updateBookPosSelection();
return;
}
@@ -143,25 +146,36 @@ class ContentsPage extends ContentsPageProps {
await this.$nextTick();
const pc = parsed.contents;
const newpc = [];
//преобразуем все, кроме первого, разделы body в title-subtitle
let curSubtitles = [];
let prevBodyIndex = -1;
for (let i = 0; i < pc.length; i++) {
const cont = pc[i];
if (prevBodyIndex != cont.bodyIndex)
curSubtitles = [];
const ims = parsed.images;
const newpc = [];
if (pc.length) {//если есть оглавление
//преобразуем все, кроме первого, разделы body в title-subtitle
let curSubtitles = [];
let prevBodyIndex = -1;
for (let i = 0; i < pc.length; i++) {
const cont = pc[i];
if (prevBodyIndex != cont.bodyIndex)
curSubtitles = [];
prevBodyIndex = cont.bodyIndex;
prevBodyIndex = cont.bodyIndex;
if (cont.bodyIndex > 1) {
if (cont.inset < 1) {
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
if (cont.bodyIndex > 1) {
if (cont.inset < 1) {
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
} else {
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
}
} else {
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
newpc.push(cont);
}
}
} else {//попробуем вытащить из images
for (let i = 0; i < ims.length; i++) {
const image = ims[i];
if (image.alt) {
newpc.push({paraIndex: image.paraIndex, title: image.alt, inset: 1, bodyIndex: 0, subtitles: []});
}
} else {
newpc.push(cont);
}
}
@@ -210,19 +224,18 @@ class ContentsPage extends ContentsPageProps {
//формируем newImages
const newImages = [];
const ims = parsed.images;
for (i = 0; i < ims.length; i++) {
const image = ims[i];
const bin = parsed.binary[image.id];
const type = (bin ? bin.type : '');
const label = `Изображение ${image.num}`;
const label = (image.alt ? image.alt : '<span style="font-size: 90%; color: #dddddd"><i>Без названия</i></span>');
const indentStyle = getIndentStyle(1);
const labelStyle = getLabelStyle(0);
const labelStyle = getLabelStyle(1);
const p = parsed.para[image.paraIndex];
newImages.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset,
indentStyle, labelStyle, type, id: image.id, local: image.local});
indentStyle, labelStyle, type, num: image.num, id: image.id, local: image.local});
}
this.images = newImages;
@@ -231,7 +244,7 @@ class ContentsPage extends ContentsPageProps {
this.selectedTab = 'images';
//выделим на bookPos
this.updateBookPosSelection(currentBook.bookPos);
this.updateBookPosSelection();
//асинхронная загрузка изображений
this.imageSrc = [];
@@ -251,8 +264,13 @@ class ContentsPage extends ContentsPageProps {
})();
}
async updateBookPosSelection(bp) {
await utils.sleep(100);
async updateBookPosSelection() {
if (!this.isVisible)
return;
await utils.sleep(50);
const bp = this.bookPos;
for (let i = 0; i < this.contents.length; i++) {
const item = this.contents[i];
const nextOffset = (i < this.contents.length - 1 ? this.contents[i + 1].offset : this.parsed.textLength);
@@ -382,6 +400,10 @@ class ContentsPage extends ContentsPageProps {
transform: rotate(90deg);
}
.image-num {
font-size: 120%;
padding-bottom: 3px;
}
.image-type {
border: 1px solid black;
border-radius: 6px;

View File

@@ -39,9 +39,9 @@
<q-icon name="la la-copy" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['copyText'] }}</q-tooltip>
</button>
<button ref="splitToPara" v-show="showToolButton['splitToPara']" class="tool-button" :class="buttonActiveClass('splitToPara')" @click="buttonClick('splitToPara')" v-ripple>
<q-icon name="la la-retweet" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['splitToPara'] }}</q-tooltip>
<button ref="convOptions" v-show="showToolButton['convOptions']" class="tool-button" :class="buttonActiveClass('convOptions')" @click="buttonClick('convOptions')" v-ripple>
<q-icon name="la la-magic" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['convOptions'] }}</q-tooltip>
</button>
<button ref="refresh" v-show="showToolButton['refresh']" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')" v-ripple>
<q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}"/>
@@ -99,7 +99,7 @@
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
<ContentsPage v-show="contentsActive" ref="contentsPage" :book-pos="bookPos" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
<ContentsPage v-show="contentsActive" ref="contentsPage" :book-pos="bookPos" :is-visible="contentsActive" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle"></ReaderDialogs>
</div>
@@ -317,6 +317,10 @@ class Reader extends Vue {
this.showToolButton = settings.showToolButton;
this.enableSitesFilter = settings.enableSitesFilter;
this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
this.splitToPara = settings.splitToPara;
this.djvuQuality = settings.djvuQuality;
this.pdfAsText = settings.pdfAsText;
this.pdfQuality = settings.pdfQuality;
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
this.$root.readerActionByKeyEvent = (event) => {
@@ -336,7 +340,7 @@ class Reader extends Vue {
let againMes = '';
if (this.isFirstNeedUpdateNotify) {
againMes = ' ЕЩЕ один раз';
againMes = ' еще один раз';
}
if (this.version != this.clientVersion)
@@ -345,9 +349,9 @@ class Reader extends Vue {
console.error(e);
} finally {
this.checkingNewVersion = false;
}
}
this.isFirstNeedUpdateNotify = false;
}
this.isFirstNeedUpdateNotify = false;
}
updateHeaderMinWidth() {
@@ -703,6 +707,12 @@ class Reader extends Vue {
}
}
convOptionsToggle() {
this.settingsToggle();
if (this.settingsActive)
this.$refs.settingsPage.selectedTab = 'convert';
}
helpToggle() {
this.helpActive = !this.helpActive;
if (this.helpActive) {
@@ -729,15 +739,9 @@ class Reader extends Vue {
}
}
refreshBook(mode) {
refreshBook() {
const mrb = this.mostRecentBook();
if (mrb) {
if (mode && mode == 'split') {
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, skipCheck: true, isText: true, force: true});
} else {
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true});
}
}
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true});
}
undoAction() {
@@ -777,7 +781,7 @@ class Reader extends Vue {
case 'scrolling':
case 'search':
case 'copyText':
case 'splitToPara':
case 'convOptions':
case 'refresh':
case 'contents':
case 'libs':
@@ -811,7 +815,6 @@ class Reader extends Vue {
case 'contents':
classResult = classDisabled;
break;
case 'splitToPara':
case 'refresh':
case 'recentBooks':
if (!this.mostRecentBookReactive)
@@ -973,10 +976,13 @@ class Reader extends Vue {
if (!book) {
book = await readerApi.loadBook({
url,
skipCheck: (opts.skipCheck ? true : false),
isText: (opts.isText ? true : false),
uploadFileName,
enableSitesFilter: this.enableSitesFilter,
uploadFileName
skipHtmlCheck: (this.splitToPara ? true : false),
isText: (this.splitToPara ? true : false),
djvuQuality: this.djvuQuality,
pdfAsText: this.pdfAsText,
pdfQuality: this.pdfQuality,
},
(state) => {
progress.setState(state);
@@ -1102,8 +1108,8 @@ class Reader extends Vue {
case 'copyText':
this.copyTextToggle();
break;
case 'splitToPara':
this.refreshBook('split');
case 'convOptions':
this.convOptionsToggle();
break;
case 'refresh':
this.refreshBook();

View File

@@ -57,37 +57,6 @@
<q-btn class="q-px-sm" dense no-caps @click="donationDialogRemind">Напомнить позже</q-btn>
</span>
</Dialog>
<Dialog ref="dialog3" v-model="liberamaTopVisible">
<template slot="header">
Здравствуйте, уважаемые читатели!
</template>
<div style="word-break: normal">
Создан новый ресурс:<br><br>
<a href="https://liberama.top" target="_blank">https://liberama.top</a>
<br><br>
Это клон читалки Omni Reader, но с некоторыми дополнениями, ориентированными в сторону более свободного обмена книгами:
<ul>
<li>добавлено новое окно "Библиотека" для свободного доступа к Флибусте и другим ресурсам по желанию читателя</li>
<li>планируется добавить возможность создания подборок книг и обмена ими между пользователями</li>
</ul>
Легко мигрировать на новый сайт можно с помощью синхронизации с сервером.
О багах и предложениях просьба сообщать на почту <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a><br><br>
Спасибо, что вы с нами!
<br><br>
<div class="row justify-center">
<q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">Помочь проекту</q-btn>
</div>
</div>
<span slot="footer">
<q-btn class="q-px-sm" dense no-caps @click="liberamaTopDialogDisable">Больше не показывать</q-btn>
</span>
</Dialog>
</div>
</template>
@@ -114,7 +83,6 @@ class ReaderDialogs extends Vue {
whatsNewVisible = false;
whatsNewContent = '';
donationVisible = false;
liberamaTopVisible = false;
created() {
this.commit = this.$store.commit;
@@ -127,14 +95,12 @@ class ReaderDialogs extends Vue {
async init() {
await this.showWhatsNew();
await this.showDonation();
await this.showLiberamaTop();
}
loadSettings() {
const settings = this.settings;
this.showWhatsNewDialog = settings.showWhatsNewDialog;
this.showDonationDialog2020 = settings.showDonationDialog2020;
this.showLiberamaTopDialog2020 = settings.showLiberamaTopDialog2020;
}
async showWhatsNew() {
@@ -171,7 +137,6 @@ class ReaderDialogs extends Vue {
openDonate() {
this.donationVisible = false;
this.liberamaTopVisible = false;
this.$emit('donate-toggle');
}
@@ -210,24 +175,8 @@ class ReaderDialogs extends Vue {
return this.$store.state.reader.donationRemindDate;
}
async showLiberamaTop() {
const today = utils.formatDate(new Date(), 'coDate');
if (this.mode == 'omnireader' && today < '2020-12-01' && this.showLiberamaTopDialog2020) {
await utils.sleep(3000);
this.liberamaTopVisible = true;
}
}
liberamaTopDialogDisable() {
this.liberamaTopVisible = false;
if (this.showLiberamaTopDialog2020) {
this.commit('reader/setSettings', { showLiberamaTopDialog2020: false });
}
}
keyHook() {
if (this.$refs.dialog1.active || this.$refs.dialog2.active || this.$refs.dialog3.active)
if (this.$refs.dialog1.active || this.$refs.dialog2.active)
return true;
return false;
}

View File

@@ -26,6 +26,7 @@
<q-tab class="tab" name="buttons" icon="la la-grip-horizontal" label="Кнопки" />
<q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
<q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
<q-tab class="tab" name="others" icon="la la-list-ul" label="Прочее" />
<q-tab class="tab" name="reset" icon="la la-broom" label="Сброс" />
<div v-show="tabsScrollable" class="q-pt-lg"/>
@@ -53,6 +54,10 @@
<div v-if="selectedTab == 'pagemove'" class="fit tab-panel">
@@include('./include/PageMoveTab.inc');
</div>
<!-- Конвертирование ------------------------------------------------------------->
<div v-if="selectedTab == 'convert'" class="fit tab-panel">
@@include('./include/ConvertTab.inc');
</div>
<!-- Прочее ---------------------------------------------------------------------->
<div v-if="selectedTab == 'others'" class="fit tab-panel">
@@include('./include/OthersTab.inc');
@@ -218,6 +223,10 @@ class SettingsPage extends Vue {
return this.$store.state.config.mode;
}
get isExternalConverter() {
return this.$store.state.config.useExternalBookConverter;
}
get settings() {
return this.$store.state.reader.settings;
}
@@ -544,7 +553,7 @@ class SettingsPage extends Vue {
margin-bottom: 5px;
}
.label-1 {
.label-1, .label-7 {
width: 75px;
}
@@ -556,7 +565,7 @@ class SettingsPage extends Vue {
width: 100px;
}
.label-1, .label-2, .label-3, .label-4, .label-5, .label-6 {
.label-1, .label-2, .label-3, .label-4, .label-5, .label-6, .label-7 {
display: flex;
flex-direction: column;
justify-content: center;

View File

@@ -0,0 +1,87 @@
<!---------------------------------------------->
<div class="q-mt-sm column items-center">
<span>Настройки конвертирования применяются ко всем</span>
<span>вновь загружаемым или обновляемым файлам</span>
</div>
<!---------------------------------------------->
<div class="part-header">HTML, XML, TXT</div>
<div class="item row">
<div class="label-7">Текст</div>
<div class="col row">
<q-checkbox v-model="splitToPara" size="xs" label="Попытаться разбить текст на параграфы">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Опция принудительно включает эвристику разбиения текста на<br>
параграфы в случае, если формат файла определен как html,<br>
xml или txt. Возможна нечитабельная разметка текста.
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-7">Сайты</div>
<div class="col row">
<q-checkbox v-model="enableSitesFilter" size="xs" label="Включить html-фильтр для сайтов">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Html-фильтр вырезает лишние элементы со<br>
страницы для определенных сайтов, таких как:<br>
samlib.ru<br>
www.fanfiction.net<br>
archiveofourown.org<br>
и других
</q-tooltip>
</q-checkbox>
</div>
</div>
<!---------------------------------------------->
<div v-if="isExternalConverter">
<div class="part-header">PDF</div>
<div class="item row">
<div class="label-7">Формат</div>
<div class="col row">
<q-checkbox v-model="pdfAsText" size="xs" label="Извлекать текст из PDF">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Пытается извлечь текст из pdf-файла и переразбить на параграфы.<br>
Размер получаемого fb2-файла при этом относительно небольшой.<br>
При отключении этой опции, pdf будет представлен как набор<br>
изображений (аналогично ковертированию djvu).
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-7">Качество</div>
<div class="col row">
<NumInput class="col-5" v-model="pdfQuality" :min="10" :max="100" :disable="pdfAsText" >
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Качество конвертирования Pdf в Fb2. Чем значение выше, тем больше<br>
размер итогового файла. Если сервер отказывается конвертировать<br>
слишком большой файл, то попробуйте понизить качество.
</q-tooltip>
</NumInput>
</div>
</div>
</div>
<!---------------------------------------------->
<div v-if="isExternalConverter">
<div class="part-header">DJVU</div>
<div class="item row">
<div class="label-7">Качество</div>
<div class="col row">
<NumInput class="col-5" v-model="djvuQuality" :min="10" :max="100">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Качество конвертирования Djvu в Fb2. Чем значение выше, тем больше<br>
размер итогового файла. Если сервер отказывается конвертировать<br>
слишком большой файл, то попробуйте понизить качество.
</q-tooltip>
</NumInput>
</div>
</div>
</div>

View File

@@ -65,22 +65,6 @@
<!---------------------------------------------->
<div class="part-header">Другое</div>
<div class="item row">
<div class="label-6">Обработка</div>
<div class="col row">
<q-checkbox v-model="enableSitesFilter" @input="needTextReload" size="xs" label="Включить html-фильтр для сайтов">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Html-фильтр вырезает лишние элементы со<br>
страницы для определенных сайтов, таких как:<br>
samlib.ru<br>
www.fanfiction.net<br>
archiveofourown.org<br>
и других
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-6">Обработка</div>
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная подготовка текста">

View File

@@ -205,6 +205,7 @@ export default class BookParser {
let attrs = sax.getAttrsSync(tail);
if (attrs.href && attrs.href.value) {
const href = attrs.href.value;
const alt = (attrs.alt && attrs.alt.value ? attrs.alt.value : '');
const {id, local} = this.imageHrefToId(href);
if (href[0] == '#') {//local
imageNum++;
@@ -214,7 +215,7 @@ export default class BookParser {
else
newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
this.images.push({paraIndex, num: imageNum, id, local});
this.images.push({paraIndex, num: imageNum, id, local, alt});
if (inPara && this.showInlineImagesInCenter)
newParagraph(' ', 1);
@@ -224,7 +225,7 @@ export default class BookParser {
dimPromises.push(getExternalImageDimensions(href));
newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
this.images.push({paraIndex, num: imageNum, id, local});
this.images.push({paraIndex, num: imageNum, id, local, alt});
}
}
}

View File

@@ -1,4 +1,18 @@
export const versionHistory = [
{
showUntil: '2020-12-17',
header: '0.9.12 (2020-12-18)',
content:
`
<ul>
<li>добавлена вкладка "Изображения" в окно оглавления</li>
<li>настройки конвертирования вынесены в отдельную вкладку</li>
<li>добавлена кнопка для быстрого доступа к настройкам конвертирования</li>
<li>улучшения работы конвертеров</li>
</ul>
`
},
{
showUntil: '2020-12-08',
header: '0.9.11 (2020-12-09)',

View File

@@ -12,7 +12,7 @@ const readerActions = {
'setPosition': 'Установить позицию',
'search': 'Найти в тексте',
'copyText': 'Скопировать текст со страницы',
'splitToPara': 'Обновить с разбиением на параграфы',
'convOptions': 'Настроить конвертирование',
'refresh': 'Принудительно обновить книгу',
'offlineMode': 'Автономный режим (без интернета)',
'contents': 'Оглавление/закладки',
@@ -41,7 +41,7 @@ const toolButtons = [
{name: 'setPosition', show: true},
{name: 'search', show: true},
{name: 'copyText', show: false},
{name: 'splitToPara', show: false},
{name: 'convOptions', show: true},
{name: 'refresh', show: true},
{name: 'contents', show: true},
{name: 'libs', show: true},
@@ -60,8 +60,8 @@ const hotKeys = [
{name: 'scrolling', codes: ['Z']},
{name: 'setPosition', codes: ['P']},
{name: 'search', codes: ['Ctrl+F']},
{name: 'copyText', codes: ['Ctrl+C']},
{name: 'splitToPara', codes: ['Shift+R']},
{name: 'copyText', codes: ['Ctrl+C']},
{name: 'convOptions', codes: ['Ctrl+M']},
{name: 'refresh', codes: ['R']},
{name: 'contents', codes: ['C']},
{name: 'libs', codes: ['L']},
@@ -252,11 +252,14 @@ const settingDefaults = {
imageHeightLines: 100,
imageFitWidth: true,
enableSitesFilter: true,
splitToPara: false,
djvuQuality: 20,
pdfAsText: true,
pdfQuality: 20,
showServerStorageMessages: true,
showWhatsNewDialog: true,
showDonationDialog2020: true,
showLiberamaTopDialog2020: true,
showNeedUpdateNotify: true,
fontShifts: {},

View File

@@ -1,6 +1,6 @@
{
"name": "Liberama",
"version": "0.9.11",
"version": "0.9.12",
"author": "Book Pauk <bookpauk@gmail.com>",
"license": "CC0-1.0",
"repository": "bookpauk/liberama",

View File

@@ -20,9 +20,12 @@ class ReaderController extends BaseController {
const workerId = this.readerWorker.loadBookUrl({
url: request.url,
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true),
skipCheck: (request.hasOwnProperty('skipCheck') ? request.skipCheck : false),
skipHtmlCheck: (request.hasOwnProperty('skipHtmlCheck') ? request.skipHtmlCheck : false),
isText: (request.hasOwnProperty('isText') ? request.isText : false),
uploadFileName: (request.hasOwnProperty('uploadFileName') ? request.uploadFileName : false),
djvuQuality: (request.hasOwnProperty('djvuQuality') ? request.djvuQuality : false),
pdfAsText: (request.hasOwnProperty('pdfAsText') ? request.pdfAsText : false),
pdfQuality: (request.hasOwnProperty('pdfQuality') ? request.pdfQuality : false),
});
const state = this.workerState.getState(workerId);
return (state ? state : {});

View File

@@ -104,7 +104,7 @@ class ConvertBase {
}
isDataXml(data) {
const str = data.toString().trim();
const str = data.slice(0, 100).toString().trim();
return (str.indexOf('<?xml version="1.0"') == 0 || str.indexOf('<?xml version=\'1.0\'') == 0 );
}

View File

@@ -16,12 +16,21 @@ class ConvertDjvu extends ConvertJpegPng {
if (!this.check(data, opts))
return false;
const {inputFiles, callback, abort} = opts;
let {inputFiles, callback, abort, djvuQuality} = opts;
djvuQuality = (djvuQuality && djvuQuality <= 100 && djvuQuality >= 10 ? djvuQuality : 20);
let jpegQuality = djvuQuality;
let tiffQuality = djvuQuality + 30;
tiffQuality = (tiffQuality < 85 ? tiffQuality : 85);
const ddjvuPath = '/usr/bin/ddjvu';
if (!await fs.pathExists(ddjvuPath))
throw new Error('Внешний конвертер ddjvu не найден');
const djvusedPath = '/usr/bin/djvused';
if (!await fs.pathExists(djvusedPath))
throw new Error('Внешний конвертер djvused не найден');
const tiffsplitPath = '/usr/bin/tiffsplit';
if (!await fs.pathExists(tiffsplitPath))
throw new Error('Внешний конвертер tiffsplitPath не найден');
@@ -36,7 +45,7 @@ class ConvertDjvu extends ConvertJpegPng {
//конвертируем в tiff
let perc = 0;
await this.execConverter(ddjvuPath, ['-format=tiff', '-quality=50', '-verbose', inputFiles.sourceFile, tifFile], () => {
await this.execConverter(ddjvuPath, ['-format=tiff', `-quality=${tiffQuality}`, '-verbose', inputFiles.sourceFile, tifFile], () => {
perc = (perc < 100 ? perc + 1 : 40);
callback(perc);
}, abort);
@@ -53,22 +62,57 @@ class ConvertDjvu extends ConvertJpegPng {
await fs.remove(tifFile);
//конвертируем в jpg
await this.execConverter(mogrifyPath, ['-quality', '20', '-scale', '2048>', '-verbose', '-format', 'jpg', `${dir}*.tif`], () => {
await this.execConverter(mogrifyPath, ['-quality', jpegQuality, '-scale', '2048>', '-verbose', '-format', 'jpg', `${dir}*.tif`], () => {
perc = (perc < 100 ? perc + 1 : 40);
callback(perc);
}, abort);
limitSize = 2*this.config.maxUploadFileSize;
let jpgFilesSize = 0;
//ищем изображения
let files = [];
await utils.findFiles(async(file) => {
if (path.extname(file) == '.jpg')
if (path.extname(file) == '.jpg') {
jpgFilesSize += (await fs.stat(file)).size;
if (jpgFilesSize > limitSize) {
throw new Error(`Файл для конвертирования слишком большой|FORLOG| jpgFilesSize: ${jpgFilesSize} > ${limitSize}`);
}
files.push({name: file, base: path.basename(file)});
}
}, dir);
files.sort((a, b) => a.base.localeCompare(b.base));
//схема документа (outline)
const djvusedResult = await this.execConverter(djvusedPath, ['-u', '-e', 'print-outline', inputFiles.sourceFile], null, abort);
const outline = [];
const lines = djvusedResult.stdout.match(/\(\s*".*"\s*?"#\d+"/g);
if (lines) {
lines.forEach(l => {
const m = l.match(/"(.*)"\s*?"#(\d+)"/);
if (m) {
const pageNum = m[2];
let s = outline[pageNum];
if (!s)
s = m[1].trim();
else
s += `${(s[s.length - 1] != '.' ? '.' : '')} ${m[1].trim()}`;
outline[pageNum] = s;
}
});
}
await utils.sleep(100);
return await super.run(data, Object.assign({}, opts, {imageFiles: files.map(f => f.name)}));
let i = 0;
const imageFiles = files.map(f => {
i++;
let alt = (outline[i] ? outline[i] : '');
return {src: f.name, alt};
});
return await super.run(data, Object.assign({}, opts, {imageFiles}));
}
}

View File

@@ -13,22 +13,30 @@ class ConvertFb2 extends ConvertBase {
}
async run(data, opts) {
let newData = data;
let newData = data.slice(0, 1024);
//Корректируем кодировку, 16-битные кодировки должны стать utf-8
//Корректируем кодировку для проверки, 16-битные кодировки должны стать utf-8
const encoding = textUtils.getEncoding(newData);
if (encoding.indexOf('UTF-16') == 0) {
newData = Buffer.from(iconv.decode(newData, encoding));
}
//Проверяем
if (!this.check(newData, opts))
return false;
//Корректируем кодировку всего объема
newData = data;
if (encoding.indexOf('UTF-16') == 0) {
newData = Buffer.from(iconv.decode(newData, encoding));
}
//Корректируем пробелы, всякие файлы попадаются :(
if (newData[0] == 32) {
newData = Buffer.from(newData.toString().trim());
}
//Окончательно корректируем кодировку
return this.checkEncoding(newData);
}

View File

@@ -45,7 +45,7 @@ class ConvertFb3 extends ConvertHtml {
.replace(/<subtitle>/g, '<br><br><fb2-subtitle>')
.replace(/<\/subtitle>/g, '</fb2-subtitle>')
;
return await super.run(Buffer.from(text), {skipCheck: true});
return await super.run(Buffer.from(text), {skipHtmlCheck: true});
}
}

View File

@@ -16,7 +16,7 @@ class ConvertHtml extends ConvertBase {
}
//из буфера обмена?
if (data.toString().indexOf('<buffer>') == 0) {
if (data.slice(0, 50).toString().indexOf('<buffer>') == 0) {
return {isText: false};
}
@@ -24,15 +24,13 @@ class ConvertHtml extends ConvertBase {
}
async run(data, opts) {
let isText = false;
if (!opts.skipCheck) {
let {isText = false, uploadFileName = ''} = opts;
if (!opts.skipHtmlCheck) {
const checkResult = this.check(data, opts);
if (!checkResult)
return false;
isText = checkResult.isText;
} else {
isText = opts.isText;
}
let titleInfo = {};
@@ -242,6 +240,9 @@ class ConvertHtml extends ConvertBase {
innerCut: new Set(['head', 'script', 'style', 'binary', 'fb2-image', 'fb2-title', 'fb2-author'])
});
if (!title)
title = uploadFileName;
titleInfo['book-title'] = title;
if (author)
titleInfo.author = {'last-name': author};

View File

@@ -27,7 +27,7 @@ class ConvertJpegPng extends ConvertBase {
} else {
const imageFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}.${inputFiles.sourceFileType.ext}`;
await fs.copy(inputFiles.sourceFile, imageFile);
files.push(imageFile);
files.push({src: imageFile});
}
//читаем изображения
@@ -55,10 +55,9 @@ class ConvertJpegPng extends ConvertBase {
let images = [];
let loading = [];
files.forEach(f => {
const image = {src: f};
images.push(image);
loading.push(loadImage(image));
files.forEach(img => {
images.push(img);
loading.push(loadImage(img));
});
await Promise.all(loading);
@@ -82,8 +81,14 @@ class ConvertJpegPng extends ConvertBase {
const img = {_n: 'binary', _attrs: {id: image.name, 'content-type': image.type}, _t: image.data};
binary.push(img);
const attrs = {'l:href': `#${image.name}`};
if (image.alt) {
image.alt = (image.alt.length > 256 ? image.alt.substring(0, 256) : image.alt);
attrs.alt = image.alt;
}
pars.push({_n: 'p', _t: ''});
pars.push({_n: 'image', _attrs: {'l:href': `#${image.name}`}});
pars.push({_n: 'image', _attrs: attrs});
}
}
pars.push({_n: 'p', _t: ''});

View File

@@ -15,7 +15,7 @@ class ConvertPdf extends ConvertHtml {
}
async run(notUsed, opts) {
if (!this.check(notUsed, opts))
if (!opts.pdfAsText || !this.check(notUsed, opts))
return false;
await this.checkExternalConverterPresent();
@@ -27,7 +27,6 @@ class ConvertPdf extends ConvertHtml {
const outFile = `${outBasename}.xml`;
const pdftohtmlPath = '/usr/bin/pdftohtml';
if (!await fs.pathExists(pdftohtmlPath))
throw new Error('Внешний конвертер pdftohtml не найден');
@@ -342,7 +341,7 @@ class ConvertPdf extends ConvertHtml {
//console.log(text);
await utils.sleep(100);
return await super.run(Buffer.from(text), {skipCheck: true, isText: true});
return await super.run(Buffer.from(text), {skipHtmlCheck: true, isText: true});
}
async getPdfTitleAndAuthor(pdfFile) {

View File

@@ -0,0 +1,115 @@
const fs = require('fs-extra');
const path = require('path');
const utils = require('../../utils');
const sax = require('../../sax');
const ConvertJpegPng = require('./ConvertJpegPng');
class ConvertPdfImages extends ConvertJpegPng {
check(data, opts) {
const {inputFiles} = opts;
return this.config.useExternalBookConverter &&
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'pdf';
}
async run(data, opts) {
if (!this.check(data, opts))
return false;
let {inputFiles, callback, abort, pdfQuality} = opts;
pdfQuality = (pdfQuality && pdfQuality <= 100 && pdfQuality >= 10 ? pdfQuality : 20);
const pdftoppmPath = '/usr/bin/pdftoppm';
if (!await fs.pathExists(pdftoppmPath))
throw new Error('Внешний конвертер pdftoppm не найден');
const pdftohtmlPath = '/usr/bin/pdftohtml';
if (!await fs.pathExists(pdftohtmlPath))
throw new Error('Внешний конвертер pdftohtml не найден');
const inpFile = inputFiles.sourceFile;
const dir = `${inputFiles.filesDir}/`;
const outBasename = `${dir}${utils.randomHexString(10)}`;
const outFile = `${outBasename}.tmp`;
//конвертируем в jpeg
let perc = 0;
await this.execConverter(pdftoppmPath, ['-jpeg', '-jpegopt', `quality=${pdfQuality},progressive=y`, inpFile, outFile], () => {
perc = (perc < 100 ? perc + 1 : 40);
callback(perc);
}, abort);
const limitSize = 2*this.config.maxUploadFileSize;
let jpgFilesSize = 0;
//ищем изображения
let files = [];
await utils.findFiles(async(file) => {
if (path.extname(file) == '.jpg') {
jpgFilesSize += (await fs.stat(file)).size;
if (jpgFilesSize > limitSize) {
throw new Error(`Файл для конвертирования слишком большой|FORLOG| jpgFilesSize: ${jpgFilesSize} > ${limitSize}`);
}
files.push({name: file, base: path.basename(file)});
}
}, dir);
files.sort((a, b) => a.base.localeCompare(b.base));
//схема документа (outline)
const outXml = `${outBasename}.xml`;
await this.execConverter(pdftohtmlPath, ['-nodrm', '-i', '-c', '-s', '-xml', inpFile, outXml], null, abort);
const outline = [];
let inOutline = 0;
let inItem = false;
let pageNum = 0;
const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
if (inOutline > 0 && inItem && pageNum) {
outline[pageNum] = text;
}
};
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
if (tag == 'outline')
inOutline++;
if (inOutline > 0 && tag == 'item') {
const attrs = sax.getAttrsSync(tail);
pageNum = (attrs.page && attrs.page.value ? attrs.page.value : 0);
inItem = true;
}
};
const onEndNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
if (tag == 'outline')
inOutline--;
if (tag == 'item')
inItem = false;
};
const dataXml = await fs.readFile(outXml);
const buf = this.decode(dataXml).toString();
sax.parseSync(buf, {
onStartNode, onEndNode, onTextNode
});
await utils.sleep(100);
//формируем список файлов
let i = 0;
const imageFiles = files.map(f => {
i++;
let alt = (outline[i] ? outline[i] : '');
return {src: f.name, alt};
});
return await super.run(data, Object.assign({}, opts, {imageFiles}));
}
}
module.exports = ConvertPdfImages;

View File

@@ -48,7 +48,7 @@ class ConvertSites extends ConvertHtml {
if (text === false)
return false;
return await super.run(Buffer.from(text), {skipCheck: true});
return await super.run(Buffer.from(text), {skipHtmlCheck: true});
}
getTitle(text) {

View File

@@ -7,6 +7,7 @@ const convertClassFactory = [
require('./ConvertEpub'),
require('./ConvertDjvu'),
require('./ConvertPdf'),
require('./ConvertPdfImages'),
require('./ConvertRtf'),
require('./ConvertDocX'),
require('./ConvertFb3'),