浏览代码

Merge branch 'release/0.4.0'

Book Pauk 6 年之前
父节点
当前提交
8491c40890

+ 20 - 0
client/components/Reader/SettingsPage/SettingsPage.vue

@@ -194,6 +194,26 @@
                                 <el-input-number v-model="addEmptyParagraphs" :min="0" :max="2"></el-input-number>
                             </el-form-item>
                             
+                            <el-form-item label="Изображения">
+                                <el-col :span="11">
+                                    <el-checkbox v-model="showImages">Показывать</el-checkbox>
+                                </el-col>
+
+                                <el-col :span="1">
+                                    &nbsp;
+                                </el-col>
+                                <el-col :span="11">
+                                    <el-tooltip :open-delay="500" effect="light" placement="top">
+                                        <template slot="content">
+                                            Определяет высоту изображения количеством строк.<br>
+                                            В случае превышения высоты, изображение будет<br>
+                                            уменьшено с сохранением пропорций так, чтобы<br>
+                                            помещаться в указанное количество строк.
+                                        </template>
+                                        <el-input-number v-model="imageHeightLines" :min="1" :max="100"></el-input-number>
+                                    </el-tooltip>
+                                </el-col>
+                            </el-form-item>
                         </el-form>
 
                         <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>

+ 57 - 5
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 = `<div class="layout" style="width: ${this.w}px; height: ${this.h}px;` + 
+        let out = `<div style="width: ${this.w}px; height: ${this.h}px;` + 
             ` position: absolute; top: ${this.fontSize*this.textShift}px; color: ${this.textColor}; font: ${font}; ${justify}` +
-            ` line-height: ${this.lineHeight}px;">`;            
+            ` 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 ? '<b>' : '');
                 tOpen += (part.style.italic ? '<i>' : '');
@@ -86,14 +91,61 @@ export default class DrawHelper {
                 } else
                     text = part.text;
 
+                if (text.trim() == '')
+                    text = `<span style="white-space: pre">${text}</span>`;
+
                 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 += `<img src="data:${bin.type};base64,${bin.data}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
+                    } 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 += `<img src="data:${bin.type};base64,${bin.data}" style="${resize}"/>`;
+                    } else {
+                        //
+                    }
+                }
             }
 
             const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '')
-            if (line.first)
-                lineText = `<span style="display: inline-block; margin-left: ${this.p}px"></span>${lineText}`;
+            if ((line.first || space) && !center) {
+                let p = (line.first ? this.p : 0);
+                p = (space ? p + this.p*space : p);
+                lineText = `<span style="display: inline-block; margin-left: ${p}px"></span>${lineText}`;
+            }
+
             if (line.last || center)
                 lineText = `<span style="display: inline-block; ${centerStyle}">${lineText}</span>`;
 

+ 4 - 0
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

+ 141 - 37
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(`<image href="${attrs.href.value}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
+                if (attrs.href.value) {
+                    if (inPara)
+                        growParagraph(`<image-inline href="${attrs.href.value}"></image-inline>`, 0);
+                    else
+                        newParagraph(`<image href="${attrs.href.value}">${' '.repeat(maxImageLineCount)}</image>`, 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(' <s> <s> <s> ', 4);
+                    newParagraph(' ', 1);
+                    space += 1;
                 }
             }
         };
@@ -201,17 +216,26 @@ export default class BookParser {
                         growParagraph(`</${tag}>`, 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 ? '<center>' : '');
             tOpen += (bold ? '<strong>' : '');
             tOpen += (italic ? '<emphasis>' : '');
-            let tClose = (italic ? '</emphasis>' : '');
+            tOpen += (space ? `<space w="${space}">` : '');
+            let tClose = (space ? '</space>' : '');
+            tClose += (italic ? '</emphasis>' : '');
             tClose += (bold ? '</strong>' : '');
             tClose += (center ? '</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) + `<image href="${attrs.href.value}" />`, 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 (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 (!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, 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);

+ 2 - 0
client/store/modules/reader.js

@@ -163,6 +163,8 @@ const settingDefaults = {
         cutEmptyParagraphs: false,
         addEmptyParagraphs: 0,
         blinkCachedLoad: true,
+        showImages: true,
+        imageHeightLines: 100,
 
         fontShifts: {},
 };

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "Liberama",
-  "version": "0.3.5",
+  "version": "0.4.0",
   "engines": {
     "node": ">=10.0.0"
   },