diff --git a/client/components/Reader/SettingsPage/SettingsPage.vue b/client/components/Reader/SettingsPage/SettingsPage.vue index 796db49e..b9d54666 100644 --- a/client/components/Reader/SettingsPage/SettingsPage.vue +++ b/client/components/Reader/SettingsPage/SettingsPage.vue @@ -194,6 +194,26 @@ + + + Показывать + + + +   + + + + + + + + diff --git a/client/components/Reader/TextPage/DrawHelper.js b/client/components/Reader/TextPage/DrawHelper.js index acc14c42..40b81a55 100644 --- a/client/components/Reader/TextPage/DrawHelper.js +++ b/client/components/Reader/TextPage/DrawHelper.js @@ -26,10 +26,11 @@ export default class DrawHelper { const font = this.fontByStyle({}); const justify = (this.textAlignJustify ? 'text-align: justify; text-align-last: justify;' : ''); - let out = `
`; + ` line-height: ${this.lineHeight}px;">`; + let imageDrawn = new Set(); let len = lines.length; const lineCount = this.pageLineCount + (isScrolling ? 1 : 0); len = (len > lineCount ? lineCount : len); @@ -43,11 +44,13 @@ export default class DrawHelper { first: Boolean, last: Boolean, parts: array of { - style: {bold: Boolean, italic: Boolean, center: Boolean} + style: {bold: Boolean, italic: Boolean, center: Boolean}, + image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number}, text: String, } }*/ let sel = new Set(); + //поиск if (i == 0 && this.searching) { let pureText = ''; for (const part of line.parts) { @@ -70,7 +73,9 @@ export default class DrawHelper { let lineText = ''; let center = false; + let space = 0; let j = 0; + //формируем строку for (const part of line.parts) { let tOpen = (part.style.bold ? '' : ''); tOpen += (part.style.italic ? '' : ''); @@ -86,14 +91,61 @@ export default class DrawHelper { } else text = part.text; + if (text.trim() == '') + text = `${text}`; + lineText += `${tOpen}${text}${tClose}`; center = center || part.style.center; + space = (part.style.space > 0 ? part.style.space : space); + + //избражения + //image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number}, + const img = part.image; + if (img && img.id && !img.inline && !imageDrawn.has(img.paraIndex)) { + if (img.local) { + const bin = this.parsed.binary[img.id]; + + let imgH = img.lineCount*this.lineHeight; + imgH = (imgH <= bin.h ? imgH : bin.h); + let imgW = bin.w; + + let resize = ''; + if (bin.h > imgH) { + resize = `height: ${imgH}px`; + imgW = imgW*imgH/bin.h; + } + + const left = (this.w - imgW)/2; + const top = ((img.lineCount*this.lineHeight - imgH)/2) + (i - img.imageLine)*this.lineHeight; + lineText += ``; + } else { + // + } + imageDrawn.add(img.paraIndex); + } + + if (img && img.id && img.inline) { + if (img.local) { + const bin = this.parsed.binary[img.id]; + let resize = ''; + if (bin.h > this.fontSize) { + resize = `height: ${this.fontSize - 3}px`; + } + lineText += ``; + } else { + // + } + } } const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '') - if (line.first) - lineText = `${lineText}`; + if ((line.first || space) && !center) { + let p = (line.first ? this.p : 0); + p = (space ? p + this.p*space : p); + lineText = `${lineText}`; + } + if (line.last || center) lineText = `${lineText}`; diff --git a/client/components/Reader/TextPage/TextPage.vue b/client/components/Reader/TextPage/TextPage.vue index 19a3583f..7e0a845d 100644 --- a/client/components/Reader/TextPage/TextPage.vue +++ b/client/components/Reader/TextPage/TextPage.vue @@ -209,6 +209,7 @@ class TextPage extends Vue { this.parsed.p = this.p; this.parsed.w = this.w;// px, ширина текста this.parsed.font = this.font; + this.parsed.fontSize = this.fontSize; this.parsed.wordWrap = this.wordWrap; this.parsed.cutEmptyParagraphs = this.cutEmptyParagraphs; this.parsed.addEmptyParagraphs = this.addEmptyParagraphs; @@ -216,6 +217,9 @@ class TextPage extends Vue { while (this.drawHelper.measureText(t, {}) < this.w) t += 'Щ'; this.parsed.maxWordLength = t.length - 1; this.parsed.measureText = this.drawHelper.measureText.bind(this.drawHelper); + this.parsed.lineHeight = this.lineHeight; + this.parsed.showImages = this.showImages; + this.parsed.imageHeightLines = this.imageHeightLines; } //statusBar diff --git a/client/components/Reader/share/BookParser.js b/client/components/Reader/share/BookParser.js index 5a4dafcb..e00fd081 100644 --- a/client/components/Reader/share/BookParser.js +++ b/client/components/Reader/share/BookParser.js @@ -39,6 +39,9 @@ export default class BookParser { let center = false; let bold = false; let italic = false; + let space = 0; + let inPara = false; + this.binary = {}; let binaryId = ''; let binaryType = ''; @@ -147,8 +150,12 @@ export default class BookParser { if (tag == 'image') { let attrs = sax.getAttrsSync(tail); - if (attrs.href.value) - newParagraph(`${' '.repeat(maxImageLineCount)}`, maxImageLineCount); + if (attrs.href.value) { + if (inPara) + growParagraph(``, 0); + else + newParagraph(`${' '.repeat(maxImageLineCount)}`, maxImageLineCount); + } } if (path.indexOf('/fictionbook/body') == 0) { @@ -158,12 +165,18 @@ export default class BookParser { center = true; } + if (tag == 'section') { + newParagraph(' ', 1); + } + if (tag == 'emphasis' || tag == 'strong') { growParagraph(`<${tag}>`, 0); } if ((tag == 'p' || tag == 'empty-line' || tag == 'v')) { newParagraph(' ', 1); + if (tag == 'p') + inPara = true; } if (tag == 'subtitle') { @@ -173,6 +186,7 @@ export default class BookParser { if (tag == 'epigraph') { italic = true; + space += 1; } if (tag == 'poem') { @@ -180,7 +194,8 @@ export default class BookParser { } if (tag == 'text-author') { - newParagraph(' ', 4); + newParagraph(' ', 1); + space += 1; } } }; @@ -201,17 +216,26 @@ export default class BookParser { growParagraph(``, 0); } + if (tag == 'p') { + inPara = false; + } + if (tag == 'subtitle') { bold = false; } if (tag == 'epigraph') { italic = false; + space -= 1; } if (tag == 'stanza') { newParagraph(' ', 1); } + + if (tag == 'text-author') { + space -= 1; + } } path = path.substr(0, path.length - tag.length - 1); @@ -273,7 +297,9 @@ export default class BookParser { let tOpen = (center ? '
' : ''); tOpen += (bold ? '' : ''); tOpen += (italic ? '' : ''); - let tClose = (italic ? '' : ''); + tOpen += (space ? `` : ''); + let tClose = (space ? '' : ''); + tClose += (italic ? '' : ''); tClose += (bold ? '' : ''); tClose += (center ? '
' : ''); @@ -348,18 +374,13 @@ export default class BookParser { splitToStyle(s) { let result = [];/*array of { - style: {bold: Boolean, italic: Boolean, center: Boolean}, - image: Boolean, - imageId: String, + style: {bold: Boolean, italic: Boolean, center: Boolean, space: Number}, + image: {local: Boolean, inline: Boolean, id: String}, text: String, }*/ let style = {}; let image = {}; - /*let attrs = sax.getAttrsSync(tail); - if (attrs.href.value) - newParagraph(' '.repeat(maxImageLineCount) + ``, maxImageLineCount); -*/ const onTextNode = async(text) => {// eslint-disable-line no-unused-vars result.push({ style: Object.assign({}, style), @@ -379,9 +400,42 @@ export default class BookParser { case 'center': style.center = true; break; - case 'image': - image = {}; + case 'space': { + let attrs = sax.getAttrsSync(tail); + if (attrs.w.value) + style.space = attrs.w.value; break; + } + case 'image': { + let attrs = sax.getAttrsSync(tail); + let id = attrs.href.value; + if (id) { + let local = false; + if (id[0] == '#') { + id = id.substr(1); + local = true; + } + image = {local, inline: false, id}; + } + break; + } + case 'image-inline': { + let attrs = sax.getAttrsSync(tail); + let id = attrs.href.value; + if (id) { + let local = false; + if (id[0] == '#') { + id = id.substr(1); + local = true; + } + result.push({ + style: Object.assign({}, style), + image: {local, inline: true, id}, + text: '' + }); + } + break; + } } }; @@ -396,9 +450,14 @@ export default class BookParser { case 'center': style.center = false; break; + case 'space': + style.space = 0; + break; case 'image': image = {}; break; + case 'image-inline': + break; } }; @@ -412,20 +471,22 @@ export default class BookParser { result = []; for (const part of parts) { let p = part; - let i = 0; - let spaceIndex = -1; - while (i < p.text.length) { - if (p.text[i] == ' ') - spaceIndex = i; + if (!p.image.id) { + let i = 0; + let spaceIndex = -1; + while (i < p.text.length) { + if (p.text[i] == ' ') + spaceIndex = i; - if (i - spaceIndex >= maxWordLength && i < p.text.length - 1 && - this.measureText(p.text.substr(spaceIndex + 1, i - spaceIndex), p.style) >= this.w - this.p) { - result.push({style: p.style, image: p.image, text: p.text.substr(0, i + 1)}); - p = {style: p.style, text: p.text.substr(i + 1)}; - spaceIndex = -1; - i = -1; + if (i - spaceIndex >= maxWordLength && i < p.text.length - 1 && + this.measureText(p.text.substr(spaceIndex + 1, i - spaceIndex), p.style) >= this.w - this.p) { + result.push({style: p.style, image: p.image, text: p.text.substr(0, i + 1)}); + p = {style: p.style, image: p.image, text: p.text.substr(i + 1)}; + spaceIndex = -1; + i = -1; + } + i++; } - i++; } result.push(p); } @@ -494,7 +555,9 @@ export default class BookParser { para.parsed.maxWordLength === this.maxWordLength && para.parsed.font === this.font && para.parsed.cutEmptyParagraphs === this.cutEmptyParagraphs && - para.parsed.addEmptyParagraphs === this.addEmptyParagraphs + para.parsed.addEmptyParagraphs === this.addEmptyParagraphs && + para.parsed.showImages === this.showImages && + para.parsed.imageHeightLines === this.imageHeightLines ) return para.parsed; @@ -506,6 +569,8 @@ export default class BookParser { font: this.font, cutEmptyParagraphs: this.cutEmptyParagraphs, addEmptyParagraphs: this.addEmptyParagraphs, + showImages: this.showImages, + imageHeightLines: this.imageHeightLines, visible: !( (this.cutEmptyParagraphs && para.cut) || (para.addIndex > this.addEmptyParagraphs) @@ -521,6 +586,7 @@ export default class BookParser { last: Boolean, parts: array of { style: {bold: Boolean, italic: Boolean, center: Boolean}, + image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number}, text: String, } }*/ @@ -531,16 +597,58 @@ export default class BookParser { let str = '';//измеряемая строка let prevStr = '';//строка без крайнего слова - let prevW = 0; let j = 0;//номер строки let style = {}; let ofs = 0;//смещение от начала параграфа para.offset + let imgW = 0; - // тут начинается самый замес, перенос по слогам и стилизация + // тут начинается самый замес, перенос по слогам и стилизация, а также изображения for (const part of parts) { - const words = part.text.split(' '); style = part.style; + //изображения + if (part.image.id && !part.image.inline) { + parsed.visible = this.showImages; + const bin = this.binary[part.image.id]; + + let lineCount = this.imageHeightLines; + const c = Math.ceil(bin.h/this.lineHeight); + lineCount = (c < lineCount ? c : lineCount); + let i = 0; + for (; i < lineCount - 1; i++) { + line.end = para.offset + ofs; + line.first = (j == 0); + line.last = false; + line.parts.push({style, text: ' ', image: { + local: part.image.local, + inline: false, + id: part.image.id, + imageLine: i, + lineCount, + paraIndex + }}); + lines.push(line); + line = {begin: line.end + 1, parts: []}; + ofs++; + j++; + } + line.first = (j == 0); + line.last = true; + line.parts.push({style, text: ' ', + image: {local: part.image.local, inline: false, id: part.image.id, imageLine: i, lineCount, paraIndex}}); + continue; + } + + if (part.image.id && part.image.inline && this.showImages) { + const bin = this.binary[part.image.id]; + let imgH = (bin.h > this.fontSize ? this.fontSize : bin.h); + imgW += bin.w*imgH/bin.h; + line.parts.push({style, text: '', + image: {local: part.image.local, inline: true, id: part.image.id}}); + } + + let words = part.text.split(' '); + let sp1 = ''; let sp2 = ''; for (let i = 0; i < words.length; i++) { @@ -552,7 +660,8 @@ export default class BookParser { str += sp1 + word; - let p = (j == 0 ? parsed.p : 0); + let p = (j == 0 ? parsed.p : 0) + imgW; + p = (style.space ? p + parsed.p*style.space : p); let w = this.measureText(str, style) + p; let wordTail = word; if (w > parsed.w && prevStr != '') { @@ -578,7 +687,6 @@ export default class BookParser { } if (pw) { - prevW = pw; partText += ss + (ss[ss.length - 1] == '-' ? '' : '-'); wordTail = slogi.join(''); } @@ -592,7 +700,6 @@ export default class BookParser { let t = line.parts[line.parts.length - 1].text; if (t[t.length - 1] == ' ') { line.parts[line.parts.length - 1].text = t.trimRight(); - prevW -= this.measureText(' ', style); } } @@ -600,7 +707,6 @@ export default class BookParser { if (line.end - line.begin < 0) console.error(`Parse error, empty line in paragraph ${paraIndex}`); - line.width = prevW; line.first = (j == 0); line.last = false; lines.push(line); @@ -609,6 +715,7 @@ export default class BookParser { partText = ''; sp2 = ''; str = wordTail; + imgW = 0; j++; } @@ -616,7 +723,6 @@ export default class BookParser { partText += sp2 + wordTail; sp1 = ' '; sp2 = ' '; - prevW = w; } if (partText != '') @@ -628,14 +734,12 @@ export default class BookParser { let t = line.parts[line.parts.length - 1].text; if (t[t.length - 1] == ' ') { line.parts[line.parts.length - 1].text = t.trimRight(); - prevW -= this.measureText(' ', style); } line.end = para.offset + para.length - 1; if (line.end - line.begin < 0) console.error(`Parse error, empty line in paragraph ${paraIndex}`); - line.width = prevW; line.first = (j == 0); line.last = true; lines.push(line); diff --git a/client/store/modules/reader.js b/client/store/modules/reader.js index 676a49b4..f00c2a56 100644 --- a/client/store/modules/reader.js +++ b/client/store/modules/reader.js @@ -163,6 +163,8 @@ const settingDefaults = { cutEmptyParagraphs: false, addEmptyParagraphs: 0, blinkCachedLoad: true, + showImages: true, + imageHeightLines: 100, fontShifts: {}, }; diff --git a/package.json b/package.json index d7dada7b..8e5830c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Liberama", - "version": "0.3.5", + "version": "0.4.0", "engines": { "node": ">=10.0.0" },