diff --git a/client/components/App.vue b/client/components/App.vue
index 44859c56..9232d1c1 100644
--- a/client/components/App.vue
+++ b/client/components/App.vue
@@ -2,7 +2,7 @@
-
+
@@ -12,8 +12,11 @@
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
+
import Notify from './share/Notify.vue';
import StdDialog from './share/StdDialog.vue';
+
+import miscApi from '../api/misc';
import * as utils from '../share/utils';
export default @Component({
@@ -30,6 +33,8 @@ export default @Component({
})
class App extends Vue {
+ showPage = false;
+
itemRuText = {
'/cardindex': 'Картотека',
'/reader': 'Читалка',
@@ -42,7 +47,6 @@ class App extends Vue {
created() {
this.commit = this.$store.commit;
- this.dispatch = this.$store.dispatch;
this.state = this.$store.state;
this.uistate = this.$store.state.uistate;
this.config = this.$store.state.config;
@@ -116,18 +120,24 @@ class App extends Vue {
this.$root.notify = this.$refs.notify;
this.$root.stdDialog = this.$refs.stdDialog;
- this.dispatch('config/loadConfig');
- this.$watch('apiError', function(newError) {
- if (newError) {
- let mes = newError.message;
- if (newError.response && newError.response.config)
- mes = newError.response.config.url + '
' + newError.response.statusText;
- this.$root.notify.error(mes, 'Ошибка API');
- }
- });
-
this.setAppTitle();
(async() => {
+ //загрузим конфиг сревера
+ try {
+ const config = await miscApi.loadConfig();
+ this.commit('config/setConfig', config);
+ this.showPage = true;
+ } catch(e) {
+ //проверим, не получен ли конфиг ранее
+ if (!this.mode) {
+ this.$root.notify.error(e.message, 'Ошибка API');
+ } else {
+ //вероятно, работаем в оффлайне
+ this.showPage = true;
+ }
+ console.error(e);
+ }
+
//запросим persistent storage
if (navigator.storage && navigator.storage.persist) {
navigator.storage.persist();
diff --git a/client/components/Reader/Reader.vue b/client/components/Reader/Reader.vue
index 09a46a36..f1ee8e8b 100644
--- a/client/components/Reader/Reader.vue
+++ b/client/components/Reader/Reader.vue
@@ -133,6 +133,9 @@ import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
import bookManager from './share/bookManager';
import rstore from '../../store/modules/reader';
import readerApi from '../../api/reader';
+import miscApi from '../../api/misc';
+
+import {versionHistory} from './versionHistory';
import * as utils from '../../share/utils';
export default @Component({
@@ -229,7 +232,6 @@ class Reader extends Vue {
this.rstore = rstore;
this.loading = true;
this.commit = this.$store.commit;
- this.dispatch = this.$store.dispatch;
this.reader = this.$store.state.reader;
this.config = this.$store.state.config;
@@ -293,6 +295,16 @@ class Reader extends Vue {
await this.$refs.dialogs.init();
})();
+
+ (async() => {
+ this.isFirstNeedUpdateNotify = true;
+ //вечный цикл, запрашиваем периодически конфиг для проверки выхода новой версии читалки
+ while (true) {// eslint-disable-line no-constant-condition
+ await this.checkNewVersionAvailable();
+ await utils.sleep(3600*1000); //каждый час
+ }
+ //дальше кода нет
+ })();
}
loadSettings() {
@@ -304,6 +316,7 @@ class Reader extends Vue {
this.blinkCachedLoad = settings.blinkCachedLoad;
this.showToolButton = settings.showToolButton;
this.enableSitesFilter = settings.enableSitesFilter;
+ this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
this.$root.readerActionByKeyEvent = (event) => {
@@ -313,6 +326,30 @@ class Reader extends Vue {
this.updateHeaderMinWidth();
}
+ async checkNewVersionAvailable() {
+ if (!this.checkingNewVersion && this.showNeedUpdateNotify) {
+ this.checkingNewVersion = true;
+ try {
+ await utils.sleep(15*1000); //подождем 15 секунд, чтобы прогрузился ServiceWorker при выходе новой версии
+ const config = await miscApi.loadConfig();
+ this.commit('config/setConfig', config);
+
+ let againMes = '';
+ if (this.isFirstNeedUpdateNotify) {
+ againMes = ' ЕЩЕ один раз';
+ }
+
+ if (this.version != this.clientVersion)
+ this.$root.notify.info(`Вышла новая версия (v${this.version}) читалки.
Пожалуйста, обновите страницу${againMes}.`, 'Обновление');
+ } catch(e) {
+ console.error(e);
+ } finally {
+ this.checkingNewVersion = false;
+ }
+ }
+ this.isFirstNeedUpdateNotify = false;
+ }
+
updateHeaderMinWidth() {
const showButtonCount = Object.values(this.showToolButton).reduce((a, b) => a + (b ? 1 : 0), 0);
if (this.$refs.buttons)
@@ -394,6 +431,16 @@ class Reader extends Vue {
return this.$store.state.config.mode;
}
+ get version() {
+ return this.$store.state.config.version;
+ }
+
+ get clientVersion() {
+ let v = versionHistory[0].header;
+ v = v.split(' ')[0];
+ return v;
+ }
+
get routeParamUrl() {
let result = '';
const path = this.$route.fullPath;
@@ -963,6 +1010,8 @@ class Reader extends Vue {
progress.hide(); this.progressActive = false;
this.loaderActive = true;
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
+ } finally {
+ this.checkNewVersionAvailable();
}
}
diff --git a/client/components/Reader/SettingsPage/include/OthersTab.inc b/client/components/Reader/SettingsPage/include/OthersTab.inc
index 46f3427b..5bc4f21b 100644
--- a/client/components/Reader/SettingsPage/include/OthersTab.inc
+++ b/client/components/Reader/SettingsPage/include/OthersTab.inc
@@ -36,7 +36,18 @@
Показывать уведомление "Что нового"
Показывать уведомления "Что нового"
- при каждом выходе новой версии читалки
+ при появлении новой версии читалки
+
+
+
+
+
+
Уведомление
+
+ Показывать уведомление о новой версии
+
+ Напоминать о необходимости обновления страницы
+ при появлении новой версии читалки
diff --git a/client/components/Reader/share/BookParser.js b/client/components/Reader/share/BookParser.js
index 028991c0..0e2bc97d 100644
--- a/client/components/Reader/share/BookParser.js
+++ b/client/components/Reader/share/BookParser.js
@@ -304,6 +304,11 @@ export default class BookParser {
bold = true;
center = true;
+ if (curTitle.paraIndex < 0) {
+ curTitle = {paraIndex, title: 'Оглавление', inset: sectionLevel, bodyIndex, subtitles: []};
+ this.contents.push(curTitle);
+ }
+
inSubtitle = true;
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
curTitle.subtitles.push(curSubtitle);
diff --git a/client/components/Reader/share/bookManager.js b/client/components/Reader/share/bookManager.js
index 76412c9c..0c93379e 100644
--- a/client/components/Reader/share/bookManager.js
+++ b/client/components/Reader/share/bookManager.js
@@ -410,16 +410,12 @@ class BookManager {
}
async setRecentBook(value) {
- const result = this.metaOnly(value);
+ let result = this.metaOnly(value);
result.touchTime = Date.now();
result.deleted = 0;
- if (this.recent[result.key] && this.recent[result.key].deleted) {
- //восстановим из небытия пользовательские данные
- if (!result.bookPos)
- result.bookPos = this.recent[result.key].bookPos;
- if (!result.bookPosSeen)
- result.bookPosSeen = this.recent[result.key].bookPosSeen;
+ if (this.recent[result.key]) {
+ result = Object.assign({}, this.recent[result.key], result);
}
await this.recentSetLastKey(result.key);
diff --git a/client/store/modules/config.js b/client/store/modules/config.js
index a9c7c1e7..32852f7f 100644
--- a/client/store/modules/config.js
+++ b/client/store/modules/config.js
@@ -10,18 +10,7 @@ const state = {
const getters = {};
// actions
-const actions = {
- async loadConfig({ commit, state }) {
- commit('setApiError', null, { root: true });
- commit('setConfig', {});
- try {
- const config = await miscApi.loadConfig();
- commit('setConfig', config);
- } catch (e) {
- commit('setApiError', e, { root: true });
- }
- },
-};
+const actions = {};
// mutations
const mutations = {
diff --git a/client/store/modules/reader.js b/client/store/modules/reader.js
index f99d79c8..ed3d10d9 100644
--- a/client/store/modules/reader.js
+++ b/client/store/modules/reader.js
@@ -251,11 +251,13 @@ const settingDefaults = {
compactTextPerc: 0,
imageHeightLines: 100,
imageFitWidth: true,
+ enableSitesFilter: true,
+
showServerStorageMessages: true,
showWhatsNewDialog: true,
showDonationDialog2020: true,
showLiberamaTopDialog2020: true,
- enableSitesFilter: true,
+ showNeedUpdateNotify: true,
fontShifts: {},
showToolButton: {},
diff --git a/docs/omnireader.ru/README.md b/docs/omnireader.ru/README.md
index 2f9aa452..45772780 100644
--- a/docs/omnireader.ru/README.md
+++ b/docs/omnireader.ru/README.md
@@ -32,23 +32,11 @@ sudo -u www-data mkdir -p /home/liberama/data/calibre
sudo -u www-data tar xvf calibre-5.5.0-x86_64.txz -C /home/liberama/data/calibre
```
-### external converter `pdfalto`, github https://github.com/kermitt2/pdfalto
-```
-git clone https://github.com/kermitt2/pdfalto
-cd pdfalto
-git submodule update --init --recursive
-cmake ./
-добавить в начало CMakeLists.txt строчку: set(CMAKE_EXE_LINKER_FLAGS "-no-pie")
-make
-
-sudo -u www-data mkdir -p /home/liberama/data/pdfalto
-sudo -u www-data cp pdfalto /home/liberama/data/pdfalto
-```
-
### external converters
```
sudo apt install rar
sudo apt install libreoffice
+sudo apt install poppler-utils
sudo apt install djvulibre-bin
sudo apt install libtiff-tools
sudo apt install graphicsmagick-imagemagick-compat
diff --git a/server/core/Reader/BookConverter/ConvertBase.js b/server/core/Reader/BookConverter/ConvertBase.js
index 0f2ba697..069830ea 100644
--- a/server/core/Reader/BookConverter/ConvertBase.js
+++ b/server/core/Reader/BookConverter/ConvertBase.js
@@ -70,6 +70,7 @@ class ConvertBase {
const error = `${result.code}|FORLOG|, exec: ${path}, args: ${args.join(' ')}, stdout: ${result.stdout}, stderr: ${result.stderr}`;
throw new Error(`Внешний конвертер завершился с ошибкой: ${error}`);
}
+ return result;
} catch(e) {
if (e.status == 'killed') {
throw new Error('Слишком долгое ожидание конвертера');
diff --git a/server/core/Reader/BookConverter/ConvertDjvu.js b/server/core/Reader/BookConverter/ConvertDjvu.js
index 28e75d8e..22f899f2 100644
--- a/server/core/Reader/BookConverter/ConvertDjvu.js
+++ b/server/core/Reader/BookConverter/ConvertDjvu.js
@@ -2,9 +2,9 @@ const fs = require('fs-extra');
const path = require('path');
const utils = require('../../utils');
-const ConvertBase = require('./ConvertBase');
+const ConvertJpegPng = require('./ConvertJpegPng');
-class ConvertDjvu extends ConvertBase {
+class ConvertDjvu extends ConvertJpegPng {
check(data, opts) {
const {inputFiles} = opts;
@@ -16,7 +16,7 @@ class ConvertDjvu extends ConvertBase {
if (!this.check(data, opts))
return false;
- const {inputFiles, callback, abort, uploadFileName} = opts;
+ const {inputFiles, callback, abort} = opts;
const ddjvuPath = '/usr/bin/ddjvu';
if (!await fs.pathExists(ddjvuPath))
@@ -31,8 +31,8 @@ class ConvertDjvu extends ConvertBase {
throw new Error('Внешний конвертер mogrifyPath не найден');
const dir = `${inputFiles.filesDir}/`;
- const inpFile = `${dir}${path.basename(inputFiles.sourceFile)}`;
- const tifFile = `${inpFile}.tif`;
+ const baseFile = `${dir}${path.basename(inputFiles.sourceFile)}`;
+ const tifFile = `${baseFile}.tif`;
//конвертируем в tiff
let perc = 0;
@@ -42,9 +42,9 @@ class ConvertDjvu extends ConvertBase {
}, abort);
const tifFileSize = (await fs.stat(tifFile)).size;
- let limitSize = 3*this.config.maxUploadFileSize;
+ let limitSize = 4*this.config.maxUploadFileSize;
if (tifFileSize > limitSize) {
- throw new Error(`Файл для конвертирования слишком большой|FORLOG| ${tifFileSize} > ${limitSize}`);
+ throw new Error(`Файл для конвертирования слишком большой|FORLOG| tifFileSize: ${tifFileSize} > ${limitSize}`);
}
//разбиваем на файлы
@@ -53,25 +53,12 @@ class ConvertDjvu extends ConvertBase {
await fs.remove(tifFile);
//конвертируем в jpg
- await this.execConverter(mogrifyPath, ['-quality', '20', '-scale', '2048', '-verbose', '-format', 'jpg', `${dir}*.tif`], () => {
+ await this.execConverter(mogrifyPath, ['-quality', '20', '-scale', '2048>', '-verbose', '-format', 'jpg', `${dir}*.tif`], () => {
perc = (perc < 100 ? perc + 1 : 40);
callback(perc);
}, abort);
- //читаем изображения
- limitSize = 2*this.config.maxUploadFileSize;
- let imagesSize = 0;
-
- const loadImage = async(image) => {
- image.data = (await fs.readFile(image.file)).toString('base64');
- image.name = path.basename(image.file);
-
- imagesSize += image.data.length;
- if (imagesSize > limitSize) {
- throw new Error(`Файл для конвертирования слишком большой|FORLOG| imagesSize: ${imagesSize} > ${limitSize}`);
- }
- }
-
+ //ищем изображения
let files = [];
await utils.findFiles(async(file) => {
if (path.extname(file) == '.jpg')
@@ -80,39 +67,8 @@ class ConvertDjvu extends ConvertBase {
files.sort((a, b) => a.base.localeCompare(b.base));
- let images = [];
- let loading = [];
- files.forEach(f => {
- const image = {file: f.name};
- images.push(image);
- loading.push(loadImage(image));
- });
-
- await Promise.all(loading);
-
- //формируем fb2
- let titleInfo = {};
- let desc = {_n: 'description', 'title-info': titleInfo};
- let pars = [];
- let body = {_n: 'body', section: {_a: [pars]}};
- let binary = [];
- let fb2 = [desc, body, binary];
-
- let title = '';
- if (uploadFileName)
- title = uploadFileName;
-
- titleInfo['book-title'] = title;
-
- for (const image of images) {
- const img = {_n: 'binary', _attrs: {id: image.name, 'content-type': 'image/jpeg'}, _t: image.data};
- binary.push(img);
-
- pars.push({_n: 'p', _t: ''});
- pars.push({_n: 'image', _attrs: {'l:href': `#${image.name}`}});
- }
-
- return this.formatFb2(fb2);
+ await utils.sleep(100);
+ return await super.run(data, Object.assign({}, opts, {imageFiles: files.map(f => f.name)}));
}
}
diff --git a/server/core/Reader/BookConverter/ConvertJpegPng.js b/server/core/Reader/BookConverter/ConvertJpegPng.js
new file mode 100644
index 00000000..8a8debeb
--- /dev/null
+++ b/server/core/Reader/BookConverter/ConvertJpegPng.js
@@ -0,0 +1,95 @@
+const fs = require('fs-extra');
+const path = require('path');
+//const utils = require('../../utils');
+
+const ConvertBase = require('./ConvertBase');
+
+class ConvertJpegPng extends ConvertBase {
+ check(data, opts) {
+ const {inputFiles} = opts;
+
+ return this.config.useExternalBookConverter &&
+ inputFiles.sourceFileType &&
+ (inputFiles.sourceFileType.ext == 'jpg' || inputFiles.sourceFileType.ext == 'png' );
+ }
+
+ async run(data, opts) {
+ const {inputFiles, uploadFileName, imageFiles} = opts;
+
+ if (!imageFiles) {
+ if (!this.check(data, opts))
+ return false;
+ }
+
+ let files = [];
+ if (imageFiles) {
+ files = imageFiles;
+ } else {
+ const imageFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}.${inputFiles.sourceFileType.ext}`;
+ await fs.copy(inputFiles.sourceFile, imageFile);
+ files.push(imageFile);
+ }
+
+ //читаем изображения
+ const limitSize = 2*this.config.maxUploadFileSize;
+ let imagesSize = 0;
+
+ const loadImage = async(image) => {
+ const src = path.parse(image.src);
+ let type = 'unknown';
+ switch (src.ext) {
+ case '.jpg': type = 'image/jpeg'; break;
+ case '.png': type = 'image/png'; break;
+ }
+ if (type != 'unknown') {
+ image.data = (await fs.readFile(image.src)).toString('base64');
+ image.type = type;
+ image.name = src.base;
+
+ imagesSize += image.data.length;
+ if (imagesSize > limitSize) {
+ throw new Error(`Файл для конвертирования слишком большой|FORLOG| imagesSize: ${imagesSize} > ${limitSize}`);
+ }
+ }
+ }
+
+ let images = [];
+ let loading = [];
+ files.forEach(f => {
+ const image = {src: f};
+ images.push(image);
+ loading.push(loadImage(image));
+ });
+
+ await Promise.all(loading);
+
+ //формируем fb2
+ let titleInfo = {};
+ let desc = {_n: 'description', 'title-info': titleInfo};
+ let pars = [];
+ let body = {_n: 'body', section: {_a: [pars]}};
+ let binary = [];
+ let fb2 = [desc, body, binary];
+
+ let title = '';
+ if (uploadFileName)
+ title = uploadFileName;
+
+ titleInfo['book-title'] = title;
+
+ for (const image of images) {
+ if (image.type) {
+ const img = {_n: 'binary', _attrs: {id: image.name, 'content-type': image.type}, _t: image.data};
+ binary.push(img);
+
+ pars.push({_n: 'p', _t: ''});
+ pars.push({_n: 'image', _attrs: {'l:href': `#${image.name}`}});
+ }
+ }
+ pars.push({_n: 'p', _t: ''});
+
+ return this.formatFb2(fb2);
+ }
+}
+
+module.exports = ConvertJpegPng;
diff --git a/server/core/Reader/BookConverter/ConvertPdf.js b/server/core/Reader/BookConverter/ConvertPdf.js
index cc122a6e..a41a31e6 100644
--- a/server/core/Reader/BookConverter/ConvertPdf.js
+++ b/server/core/Reader/BookConverter/ConvertPdf.js
@@ -5,7 +5,6 @@ const path = require('path');
const sax = require('../../sax');
const utils = require('../../utils');
const ConvertHtml = require('./ConvertHtml');
-const xmlParser = require('../../xmlParser');
class ConvertPdf extends ConvertHtml {
check(data, opts) {
@@ -26,16 +25,15 @@ class ConvertPdf extends ConvertHtml {
const inpFile = inputFiles.sourceFile;
const outBasename = `${inputFiles.filesDir}/${utils.randomHexString(10)}`;
const outFile = `${outBasename}.xml`;
- const metaFile = `${outBasename}_metadata.xml`;
- const pdfaltoPath = `${this.config.dataDir}/pdfalto/pdfalto`;
+ const pdftohtmlPath = '/usr/bin/pdftohtml';
- if (!await fs.pathExists(pdfaltoPath))
- throw new Error('Внешний конвертер pdfalto не найден');
+ if (!await fs.pathExists(pdftohtmlPath))
+ throw new Error('Внешний конвертер pdftohtml не найден');
//конвертируем в xml
let perc = 0;
- await this.execConverter(pdfaltoPath, [inpFile, outFile], () => {
+ await this.execConverter(pdftohtmlPath, ['-nodrm', '-c', '-s', '-xml', inpFile, outFile], () => {
perc = (perc < 80 ? perc + 10 : 40);
callback(perc);
}, abort);
@@ -57,8 +55,10 @@ class ConvertPdf extends ConvertHtml {
let images = [];
let loading = [];
- let title = '';
- let author = '';
+ let inText = false;
+ let bold = false;
+ let italic = false;
+
let i = -1;
const loadImage = async(image) => {
@@ -85,22 +85,30 @@ class ConvertPdf extends ConvertHtml {
}
};
+ const isTextBold = (text) => {
+ const m = text.trim().match(/^(.*)<\/b>$/);
+ return m && !m[1].match(/|<\/b>||<\/i>/g);
+ };
+
+ const isTextEmpty = (text) => {
+ return text.replace(/|<\/b>||<\/i>/g, '').trim() == '';
+ };
+
const putPageLines = () => {
- pagelines.sort((a, b) => (a.top - b.top)*10000 + (a.left - b.left))
+ pagelines.sort((a, b) => (Math.abs(a.top - b.top) > 3 ? a.top - b.top : 0)*10000 + (a.left - b.left))
//объединяем в одну строку равные по высоте
const pl = [];
let pt = 0;
let j = -1;
pagelines.forEach(line => {
- //добавим закрывающий тег стиля
- line.text += line.tClose;
+ if (isTextEmpty(line.text))
+ return;
//проверим, возможно это заголовок
- if (line.fonts.length == 1 && line.pageWidth) {
- const f = (line.fonts.length ? fonts[line.fonts[0]] : null);
+ if (line.fontId && line.pageWidth) {
const centerLeft = (line.pageWidth - line.width)/2;
- if (f && f.isBold && Math.abs(centerLeft - line.left) < 3) {
+ if (isTextBold(line.text) && Math.abs(centerLeft - line.left) < 10) {
if (!sectionTitleFound) {
line.isSectionTitle = true;
sectionTitleFound = true;
@@ -128,8 +136,8 @@ class ConvertPdf extends ConvertHtml {
//добавим пустую строку, если надо
const prevLine = (i > lastIndex ? lines[i] : {fonts: [], top: 0});
if (prevLine && !prevLine.isImage) {
- const f = (prevLine.fonts.length ? fonts[prevLine.fonts[0]] : (line.fonts.length ? fonts[line.fonts[0]] : null));
- if (f && f.fontSize && !line.isImage && line.top - prevLine.top > f.fontSize*1.8) {
+ const f = (prevLine.fontId ? fonts[prevLine.fontId] : (line.fontId ? fonts[line.fontId] : null));
+ if (f && f.fontSize && !line.isImage && line.top - prevLine.top > f.fontSize * 1.8) {
i++;
lines[i] = {text: '
'};
}
@@ -142,29 +150,26 @@ class ConvertPdf extends ConvertHtml {
putImage(100000);
};
+ const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
+ if (!cutCounter && inText) {
+ let tOpen = (bold ? '' : '');
+ tOpen += (italic ? '' : '');
+ let tClose = (italic ? '' : '');
+ tClose += (bold ? '' : '');
+
+ line.text += ` ${tOpen}${text}${tClose}`;
+ }
+ };
+
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
- if (tag == 'textstyle') {
- const attrs = sax.getAttrsSync(tail);
- const fontId = (attrs.id && attrs.id.value ? attrs.id.value : '');
- const fontStyle = (attrs.fontstyle && attrs.fontstyle.value ? attrs.fontstyle.value : '');
- const fontSize = (attrs.fontsize && attrs.fontsize.value ? attrs.fontsize.value : '');
-
- if (fontId) {
- const styleTags = {bold: 'b', italics: 'i', superscript: 'sup', subscript: 'sub'};
- const f = fonts[fontId] = {tOpen: '', tClose: '', isBold: false, fontSize};
-
- if (fontStyle) {
- const styles = fontStyle.split(' ');
- styles.forEach(style => {
- const s = styleTags[style];
- if (s) {
- f.tOpen += `<${s}>`;
- f.tClose = `${s}>${f.tClose}`;
- if (s == 'b')
- f.isBold = true;
- }
- });
- }
+ if (inText) {
+ switch (tag) {
+ case 'i':
+ italic = true;
+ break;
+ case 'b':
+ bold = true;
+ break;
}
}
@@ -177,80 +182,78 @@ class ConvertPdf extends ConvertHtml {
putPageLines();
}
- if (tag == 'textline') {
+ if (tag == 'fontspec') {
+ const attrs = sax.getAttrsSync(tail);
+ const fontId = (attrs.id && attrs.id.value ? attrs.id.value : '');
+ const fontSize = (attrs.size && attrs.size.value ? attrs.size.value : '');
+
+ if (fontId) {
+ fonts[fontId] = {fontSize};
+
+ }
+ }
+
+ if (tag == 'text' && !inText) {
const attrs = sax.getAttrsSync(tail);
line = {
text: '',
- top: parseInt((attrs.vpos && attrs.vpos.value ? attrs.vpos.value : null), 10),
- left: parseInt((attrs.hpos && attrs.hpos.value ? attrs.hpos.value : null), 10),
+ top: parseInt((attrs.top && attrs.top.value ? attrs.top.value : null), 10),
+ left: parseInt((attrs.left && attrs.left.value ? attrs.left.value : null), 10),
width: parseInt((attrs.width && attrs.width.value ? attrs.width.value : null), 10),
height: parseInt((attrs.height && attrs.height.value ? attrs.height.value : null), 10),
- tOpen: '',
- tClose: '',
isSectionTitle: false,
isSubtitle: false,
pageWidth: page.width,
- fonts: [],
+ fontId: (attrs.font && attrs.font.value ? attrs.font.value : ''),
};
if (line.width != 0 || line.height != 0) {
+ inText = true;
pagelines.push(line);
}
}
- if (tag == 'string') {
+ if (tag == 'image') {
const attrs = sax.getAttrsSync(tail);
- if (attrs.content && attrs.content.value) {
+ let src = (attrs.src && attrs.src.value ? attrs.src.value : '');
+ if (src) {
+ const image = {
+ isImage: true,
+ src,
+ data: '',
+ type: '',
+ top: parseInt((attrs.top && attrs.top.value ? attrs.top.value : null), 10) || 0,
+ left: parseInt((attrs.left && attrs.left.value ? attrs.left.value : null), 10) || 0,
+ width: parseInt((attrs.width && attrs.width.value ? attrs.width.value : null), 10) || 0,
+ height: parseInt((attrs.height && attrs.height.value ? attrs.height.value : null), 10) || 0,
+ };
- let tOpen = '';
- let tClose = '';
- const fontId = (attrs.stylerefs && attrs.stylerefs.value ? attrs.stylerefs.value : '');
- if (fontId && fonts[fontId]) {
- tOpen = fonts[fontId].tOpen;
- tClose = fonts[fontId].tClose;
- if (!line.fonts.length || line.fonts[0] != fontId)
- line.fonts.push(fontId);
- }
-
- if (line.tOpen != tOpen) {
- line.text += line.tClose + tOpen;
- line.tOpen = tOpen;
- line.tClose = tClose;
- }
-
- line.text += `${line.text.length ? ' ' : ''}${attrs.content.value}`;
- }
- }
-
- if (tag == 'illustration') {
- const attrs = sax.getAttrsSync(tail);
- if (attrs.type && attrs.type.value == 'image') {
- let src = (attrs.fileid && attrs.fileid.value ? attrs.fileid.value : '');
- if (src) {
- const image = {
- isImage: true,
- src,
- data: '',
- type: '',
- top: parseInt((attrs.vpos && attrs.vpos.value ? attrs.vpos.value : null), 10) || 0,
- left: parseInt((attrs.hpos && attrs.hpos.value ? attrs.hpos.value : null), 10) || 0,
- width: parseInt((attrs.width && attrs.width.value ? attrs.width.value : null), 10) || 0,
- height: parseInt((attrs.height && attrs.height.value ? attrs.height.value : null), 10) || 0,
- };
- const exists = images.filter(img => (img.top == image.top && img.left == image.left && img.width == image.width && img.height == image.height));
- if (!exists.length) {
- loading.push(loadImage(image));
- images.push(image);
- images.sort((a, b) => (a.top - b.top)*10000 + (a.left - b.left));
- }
- }
+ loading.push(loadImage(image));
+ images.push(image);
+ images.sort((a, b) => (a.top - b.top)*10000 + (a.left - b.left));
}
}
};
+ const onEndNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
+ if (inText) {
+ switch (tag) {
+ case 'i':
+ italic = false;
+ break;
+ case 'b':
+ bold = false;
+ break;
+ }
+ }
+
+ if (tag == 'text')
+ inText = false;
+ };
+
let buf = this.decode(data).toString();
sax.parseSync(buf, {
- onStartNode
+ onStartNode, onEndNode, onTextNode
});
putPageLines();
@@ -277,16 +280,8 @@ class ConvertPdf extends ConvertHtml {
}
indents[0] = 0;
- //title
- if (fs.pathExists(metaFile)) {
- const metaXmlString = (await fs.readFile(metaFile)).toString();
- let metaXmlParsed = xmlParser.parseXml(metaXmlString);
- metaXmlParsed = xmlParser.simplifyXmlParsed(metaXmlParsed);
- if (metaXmlParsed.metadata) {
- title = (metaXmlParsed.metadata.title ? metaXmlParsed.metadata.title._t : '');
- author = (metaXmlParsed.metadata.author ? metaXmlParsed.metadata.author._t : '');
- }
- }
+ //author & title
+ let {author, title} = await this.getPdfTitleAndAuthor(inpFile);
if (!title && uploadFileName)
title = uploadFileName;
@@ -302,6 +297,7 @@ class ConvertPdf extends ConvertHtml {
let concat = '';
let sp = '';
+ let firstLine = true;
for (const line of lines) {
if (text.length > limitSize) {
throw new Error(`Файл для конвертирования слишком большой|FORLOG| text.length: ${text.length} > ${limitSize}`);
@@ -313,10 +309,15 @@ class ConvertPdf extends ConvertHtml {
}
if (line.isSectionTitle) {
- text += `${line.text.trim()}`;
+ if (firstLine)
+ text += `${line.text.trim()}`;
+ else
+ text += `${line.text.trim()}`;
continue;
}
+ firstLine = false;
+
if (line.isSubtitle) {
text += `
${line.text.trim()}`;
continue;
@@ -343,6 +344,32 @@ class ConvertPdf extends ConvertHtml {
await utils.sleep(100);
return await super.run(Buffer.from(text), {skipCheck: true, isText: true});
}
+
+ async getPdfTitleAndAuthor(pdfFile) {
+ const result = {author: '', title: ''};
+
+ const pdfinfoPath = '/usr/bin/pdfinfo';
+
+ if (!await fs.pathExists(pdfinfoPath))
+ throw new Error('Внешний конвертер pdfinfo не найден');
+
+ const execResult = await this.execConverter(pdfinfoPath, [pdfFile]);
+
+ const titlePrefix = 'Title:';
+ const authorPrefix = 'Author:';
+
+ const stdout = execResult.stdout.split("\n");
+ stdout.forEach(line => {
+ if (line.indexOf(titlePrefix) == 0)
+ result.title = line.substring(titlePrefix.length).trim();
+
+ if (line.indexOf(authorPrefix) == 0)
+ result.author = line.substring(authorPrefix.length).trim();
+ });
+
+ return result;
+ }
}
+
module.exports = ConvertPdf;
diff --git a/server/core/Reader/BookConverter/index.js b/server/core/Reader/BookConverter/index.js
index ae753bac..2f9e9abb 100644
--- a/server/core/Reader/BookConverter/index.js
+++ b/server/core/Reader/BookConverter/index.js
@@ -3,6 +3,7 @@ const FileDetector = require('../../FileDetector');
//порядок важен
const convertClassFactory = [
+ require('./ConvertJpegPng'),
require('./ConvertEpub'),
require('./ConvertDjvu'),
require('./ConvertPdf'),