diff --git a/client/components/Reader/TextPage/DrawHelper.js b/client/components/Reader/TextPage/DrawHelper.js index 5242e01d..36e81590 100644 --- a/client/components/Reader/TextPage/DrawHelper.js +++ b/client/components/Reader/TextPage/DrawHelper.js @@ -3,69 +3,96 @@ export default class DrawHelper { return `${size}px ${this.fontName}`; } - drawPercentBar(context, x, y, w, h, bookPos, textLength) { + drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength) { const pad = 3; const fh = h - 2*pad; const fh2 = fh/2; const t1 = `${Math.floor((bookPos + 1)/1000)}k/${Math.floor(textLength/1000)}k`; - const w1 = context.measureText(t1).width + fh2; + const w1 = this.measureTextFont(t1, font) + fh2; const read = (bookPos + 1)/textLength; const t2 = `${(read*100).toFixed(2)}%`; - const w2 = context.measureText(t2).width; + const w2 = this.measureTextFont(t2, font); let w3 = w - w1 - w2; + let out = ''; if (w1 + w2 <= w) - context.fillText(t1, x, y + h - 2); + out += this.fillTextShift(t1, x, y, font, fontSize); if (w1 + w2 + w3 <= w && w3 > (10 + fh2)) { const barWidth = w - w1 - w2 - fh2; - context.strokeRect(x + w1, y + pad + 1, barWidth, fh - 2); - context.fillRect(x + w1 + 2, y + pad + 3, (barWidth - 4)*read, fh - 6); + out += this.strokeRect(x + w1, y + pad, barWidth, fh - 2, this.statusBarColor); + out += this.fillRect(x + w1 + 2, y + pad + 2, (barWidth - 4)*read, fh - 6, this.statusBarColor); } if (w1 <= w) - context.fillText(t2, x + w1 + w3, y + h - 2); + out += this.fillTextShift(t2, x + w1 + w3, y, font, fontSize); + + return out; } - async drawStatusBar(context, statusBarTop, statusBarHeight, statusBarColor, bookPos, textLength, title) { - const y = (statusBarTop ? 1 : this.realHeight - statusBarHeight); + drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title) { - context.fillStyle = this.backgroundColor; - context.fillRect(0, y, this.realWidth, statusBarHeight); + let out = `
`; - context.font = 'bold ' + this.fontBySize(statusBarHeight - 6); - context.fillStyle = statusBarColor; - context.strokeStyle = statusBarColor; + const fontSize = statusBarHeight*0.75; + const font = 'bold ' + this.fontBySize(fontSize); - context.fillRect(0, (statusBarTop ? statusBarHeight : y), this.realWidth, 1); + out += this.fillRect(0, (statusBarTop ? statusBarHeight : 0), this.realWidth, 1, this.statusBarColor); const date = new Date(); - const time = ` ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')} `; - const timeW = context.measureText(time).width; - context.fillText(time, this.realWidth - timeW, y + statusBarHeight - 2); + const time = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; + const timeW = this.measureTextFont(time, font); + out += this.fillTextShift(time, this.realWidth - timeW - fontSize, 2, font, fontSize); - title = ' ' + title; - context.fillText(this.fittingString(context, title, this.realWidth/2 - 3), 0, y + statusBarHeight - 2); + out += this.fillTextShift(this.fittingString(title, this.realWidth/2 - fontSize - 3, font), fontSize, 2, font, fontSize); - this.drawPercentBar(context, this.realWidth/2, y, this.realWidth/2 - timeW, statusBarHeight, bookPos, textLength); + out += this.drawPercentBar(this.realWidth/2, 2, this.realWidth/2 - timeW - 2*fontSize, statusBarHeight, font, fontSize, bookPos, textLength); + + out += '
'; + return out; } - fittingString(context, str, maxWidth) { - let w = context.measureText(str).width; + statusBarClickable(statusBarTop, statusBarHeight) { + return `
`; + } + + fittingString(str, maxWidth, font) { + let w = this.measureTextFont(str, font); const ellipsis = '…'; - const ellipsisWidth = context.measureText(ellipsis).width; + const ellipsisWidth = this.measureTextFont(ellipsis, font); if (w <= maxWidth || w <= ellipsisWidth) { return str; } else { let len = str.length; while (w >= maxWidth - ellipsisWidth && len-- > 0) { str = str.substring(0, len); - w = context.measureText(str).width; + w = this.measureTextFont(str, font); } return str + ellipsis; } } + fillTextShift(text, x, y, font, size, css) { + return this.fillText(text, x, y + size*this.fontShift, font, css); + } + fillText(text, x, y, font, css) { + css = (css ? css : ''); + return `
${text}
`; + } + + fillRect(x, y, w, h, color) { + return `
`; + } + + strokeRect(x, y, w, h, color) { + return `
`; + } } \ No newline at end of file diff --git a/client/components/Reader/TextPage/TextPage.vue b/client/components/Reader/TextPage/TextPage.vue index 90ad6a61..49acec3b 100644 --- a/client/components/Reader/TextPage/TextPage.vue +++ b/client/components/Reader/TextPage/TextPage.vue @@ -1,15 +1,22 @@ @@ -33,6 +40,11 @@ export default @Component({ }) class TextPage extends Vue { activeCanvas = false; + showStatusBar = false; + page1 = null; + page2 = null; + statusBar = null; + statusBarClickable = null; lastBook = null; bookPos = 0; @@ -63,13 +75,16 @@ class TextPage extends Vue { this.prepareNextPage(); }, 100); + this.debouncedDrawStatusBar = _.throttle(() => { + this.drawStatusBar(); + }, 60); + this.$root.$on('resize', () => {this.$nextTick(this.onResize)}); this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); } mounted() { - this.canvas1 = this.$refs.canvas1; - this.canvas2 = this.$refs.canvas2; + this.context = this.$refs.offscreenCanvas.getContext('2d'); } hex2rgba(hex, alpha = 1) { @@ -78,36 +93,12 @@ class TextPage extends Vue { } async calcDrawProps() { - this.context1 = this.canvas1.getContext('2d'); - this.context2 = this.canvas2.getContext('2d'); - this.realWidth = this.$refs.main.clientWidth; this.realHeight = this.$refs.main.clientHeight; - let ratio = window.devicePixelRatio; - if (ratio) { - this.canvas1.width = this.realWidth*ratio; - this.canvas1.height = this.realHeight*ratio; - this.canvas1.style.width = this.$refs.main.clientWidth + 'px'; - this.canvas1.style.height = this.$refs.main.clientHeight + 'px'; - this.context1.scale(ratio, ratio); + this.$refs.layoutEvents.style.width = this.realWidth + 'px'; + this.$refs.layoutEvents.style.height = this.realHeight + 'px'; - this.canvas2.width = this.realWidth*ratio; - this.canvas2.height = this.realHeight*ratio; - this.canvas2.style.width = this.$refs.main.clientWidth + 'px'; - this.canvas2.style.height = this.$refs.main.clientHeight + 'px'; - this.context2.scale(ratio, ratio); - } else { - this.canvas1.width = this.realWidth; - this.canvas1.height = this.realHeight; - this.canvas2.width = this.realWidth; - this.canvas2.height = this.realHeight; - } - - this.context1.textAlign = 'left'; - this.context2.textAlign = 'left'; - this.context1.textBaseline = 'bottom'; - this.context2.textBaseline = 'bottom'; this.activeCanvas = false; this.w = this.realWidth - 2*this.indent; @@ -120,13 +111,13 @@ class TextPage extends Vue { this.parsed.w = this.w;// px, ширина текста this.parsed.font = this.font; this.parsed.wordWrap = this.wordWrap; - this.parsed.context = this.context1; - this.parsed.fontByStyle = this.fontByStyle; + this.parsed.measureText = this.measureText; } this.statusBarColor = this.hex2rgba(this.textColor, this.statusBarColorAlpha); this.currentTransition = ''; this.pageChangeDirectionDown = true; + this.fontShift = (this.fontShifts[this.fontName] ? this.fontShifts[this.fontName] : 0)/100; //drawHelper this.drawHelper.realWidth = this.realWidth; @@ -135,6 +126,24 @@ class TextPage extends Vue { this.drawHelper.backgroundColor = this.backgroundColor; this.drawHelper.statusBarColor = this.statusBarColor; this.drawHelper.fontName = this.fontName; + this.drawHelper.fontShift = this.fontShift; + this.drawHelper.measureText = this.measureText; + this.drawHelper.measureTextFont = this.measureTextFont; + + this.$refs.statusBar.style.left = '0px'; + this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px'; + + this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight); + } + + measureText(text, style) {// eslint-disable-line no-unused-vars + this.context.font = this.fontByStyle(style); + return this.context.measureText(text).width; + } + + measureTextFont(text, font) {// eslint-disable-line no-unused-vars + this.context.font = font; + return this.context.measureText(text).width; } async loadFonts() { @@ -157,15 +166,28 @@ class TextPage extends Vue { this.linesDown = null; //preloaded fonts - this.fontList = ['12px ReaderDefault', '12px Arial', '12px ComicSansMS', '12px OpenSans', '12px Roboto', '12px ArialNarrow', - '12px Georgia', '12px Tahoma', '12px Helvetica', '12px CenturySchoolbook']; + this.fontShifts = {//% + ReaderDefault: 0, + Arial: 5, + ComicSansMS: -12, + OpenSans: 0, + Roboto: 0, + ArialNarrow: 0, + Georgia: 0, + Tahoma: 0, + Helvetica: 0, + CenturySchoolbook: 0, + } + this.fontList = []; + for (let fontName in this.fontShifts) + this.fontList.push(`12px ${fontName}`); //default draw props this.textColor = '#000000'; this.backgroundColor = '#478355'; this.fontStyle = '';// 'bold','italic' this.fontSize = 35;// px - this.fontName = 'Arial'; + this.fontName = 'ComicSansMS'; this.lineInterval = 7;// px, межстрочный интервал this.textAlignJustify = true;// выравнивание по ширине this.p = 50;// px, отступ параграфа @@ -231,22 +253,6 @@ class TextPage extends Vue { return `${style.italic ? 'italic' : ''} ${style.bold ? 'bold' : ''} ${this.fontSize}px ${this.fontName}`; } - get context() { - return (this.activeCanvas ? this.context1 : this.context2); - } - - get canvas() { - return (this.activeCanvas ? this.canvas1 : this.canvas2); - } - - get canvasStyle1() { - return (this.activeCanvas ? {'z-index': 11} : {'z-index': 10}); - } - - get canvasStyle2() { - return (this.activeCanvas ? {'z-index': 10} : {'z-index': 11}); - } - draw(immediate) { if (this.book && this.bookPos > 0 && this.bookPos >= this.parsed.textLength) { this.doEnd(); @@ -254,10 +260,12 @@ class TextPage extends Vue { } this.activeCanvas = !this.activeCanvas; - const context = this.context; - if (immediate) { - this.drawPage(context, this.bookPos); + if (immediate) { + if (this.activeCanvas) + this.page1 = this.drawPage(this.bookPos); + else + this.page2 = this.drawPage(this.bookPos); } else { if (this.pageChangeDirectionDown && this.pagePrepared && this.bookPos == this.bookPosPrepared) { this.linesDown = this.linesDownNext; @@ -265,7 +273,10 @@ class TextPage extends Vue { this.pagePrepared = false; this.debouncedPrepareNextPage(); } else { - this.drawPage(context, this.bookPos); + if (this.activeCanvas) + this.page1 = this.drawPage(this.bookPos); + else + this.page2 = this.drawPage(this.bookPos); this.pagePrepared = false; this.debouncedPrepareNextPage(); } @@ -282,21 +293,23 @@ class TextPage extends Vue { this.currentTransition = ''; this.pageChangeDirectionDown = false;//true только если PgDown } + + this.debouncedDrawStatusBar(); } - drawPage(context, bookPos, nextChangeLines) { + drawPage(bookPos, nextChangeLines) { if (!this.lastBook) return; - context.fillStyle = this.backgroundColor; - context.fillRect(0, 0, this.realWidth, this.realHeight); + let out = `
`; - if (!this.book || !this.parsed.textLength) - return; + if (!this.book || !this.parsed.textLength) { + out += '
'; + return out; + } - context.font = this.font; - context.fillStyle = this.textColor; - const spaceWidth = context.measureText(' ').width; + const spaceWidth = this.measureText(' ', {}); const lines = this.parsed.getLines(bookPos, 2*this.pageLineCount); if (!nextChangeLines) { @@ -307,7 +320,7 @@ class TextPage extends Vue { this.linesUpNext = this.parsed.getLines(bookPos, -2*this.pageLineCount); } - let y = -this.lineInterval/2 + (this.h - this.pageLineCount*this.lineHeight)/2; + let y = -this.lineInterval/2 + (this.h - this.pageLineCount*this.lineHeight)/2 + this.fontSize*this.fontShift; if (this.showStatusBar) y += this.statusBarHeight*(this.statusBarTop ? 1 : 0); @@ -328,13 +341,15 @@ class TextPage extends Vue { }*/ let indent = this.indent + (line.first ? this.p : 0); - y += this.lineHeight; let lineText = ''; let center = false; + let centerStyle = {}; for (const part of line.parts) { lineText += part.text; center = center || part.style.center; + if (part.style.center) + centerStyle = part.style.center; } let filled = false; @@ -349,13 +364,13 @@ class TextPage extends Vue { let x = indent; for (const part of line.parts) { - context.font = this.fontByStyle(part.style); + const font = this.fontByStyle(part.style); let partWords = part.text.split(' '); for (let i = 0; i < partWords.length; i++) { let word = partWords[i]; - context.fillText(word, x, y); - x += context.measureText(word).width + (i < partWords.length - 1 ? space : 0); + out += this.drawHelper.fillText(word, x, y, font); + x += this.measureText(word, part.style) + (i < partWords.length - 1 ? space : 0); } } filled = true; @@ -365,31 +380,31 @@ class TextPage extends Vue { // просто выводим текст if (!filled) { let x = indent; - x = (center ? this.indent + (this.w - context.measureText(lineText).width)/2 : x); + x = (center ? this.indent + (this.w - this.measureText(lineText, centerStyle))/2 : x); for (const part of line.parts) { let text = part.text; - context.font = this.fontByStyle(part.style); - context.fillText(text, x, y); - x += context.measureText(text).width; + const font = this.fontByStyle(part.style); + out += this.drawHelper.fillText(text, x, y, font); + x += this.measureText(text, part.style); } } + y += this.lineHeight; } - this.drawStatusBar(context, lines); + out += ''; + return out; } - drawStatusBar(context, lines) { - if (!lines) - lines = this.linesDown; - - if (this.showStatusBar) { + drawStatusBar() { + if (this.showStatusBar && this.linesDown) { + const lines = this.linesDown; let i = this.pageLineCount; if (this.keepLastToFirst) i--; i = (i > lines.length - 1 ? lines.length - 1 : i); - this.drawHelper.drawStatusBar(context, this.statusBarTop, this.statusBarHeight, - this.statusBarColor, lines[i].end, this.parsed.textLength, this.title); + this.statusBar = this.drawHelper.drawStatusBar(this.statusBarTop, this.statusBarHeight, + lines[i].end, this.parsed.textLength, this.title); } } @@ -399,7 +414,7 @@ class TextPage extends Vue { await sleep(60*1000); if (this.book && this.parsed.textLength) { - this.drawStatusBar(this.context); + this.debouncedDrawStatusBar(); } this.timeRefreshing = false; this.refreshTime(); @@ -427,8 +442,10 @@ class TextPage extends Vue { if (i >= 0 && this.linesDown.length > i) { this.bookPosPrepared = this.linesDown[i].begin; - const ctx = (!this.activeCanvas ? this.context1 : this.context2); - this.drawPage(ctx, this.bookPosPrepared, true); + if (this.activeCanvas) + this.page2 = this.drawPage(this.bookPosPrepared, true);//наоборот + else + this.page1 = this.drawPage(this.bookPosPrepared, true); this.pagePrepared = true; } @@ -606,30 +623,8 @@ class TextPage extends Vue { } } - checkPointInStatusBar(pointX, pointY) { - let titleBar = {x1: 0, y1: 0, x2: this.realWidth/2, y2: this.statusBarHeight + 1}; - if (!this.statusBarTop) { - titleBar.y1 += this.realHeight - this.statusBarHeight + 1; - titleBar.y2 += this.realHeight - this.statusBarHeight + 1; - } - - if (pointX >= titleBar.x1 && pointX <= titleBar.x2 && - pointY >= titleBar.y1 && pointY <= titleBar.y2) { - return true; - } - return false; - } - - onMouseClick(event) { - if (this.showStatusBar && this.book) { - const pointX = event.pageX - this.canvas.offsetLeft; - const pointY = event.pageY - this.canvas.offsetTop; - - if (this.checkPointInStatusBar(pointX, pointY)) { - window.open(this.meta.url, '_blank'); - return false; - } - } + onStatusBarClick() { + window.open(this.meta.url, '_blank'); } handleClick(pointX, pointY) { @@ -639,12 +634,6 @@ class TextPage extends Vue { 100: {30: 'PgUp', 100: 'PgDown'} }; - if (this.showStatusBar && this.book) { - if (this.checkPointInStatusBar(pointX, pointY)) { - return false; - } - } - const w = pointX/this.realWidth*100; const h = pointY/this.realHeight*100; @@ -695,9 +684,15 @@ class TextPage extends Vue { position: relative; } -.canvas { +.layout { margin: 0; padding: 0; position: absolute; + z-index: 10; +} + +.events { + z-index: 20; + background-color: rgba(0,0,0,0); } diff --git a/client/components/Reader/share/BookParser.js b/client/components/Reader/share/BookParser.js index 62174a39..a9faa271 100644 --- a/client/components/Reader/share/BookParser.js +++ b/client/components/Reader/share/BookParser.js @@ -8,15 +8,9 @@ export default class BookParser { this.w = 300;// px, ширина страницы this.wordWrap = false;// перенос по слогам + //заглушка this.measureText = (text, style) => {// eslint-disable-line no-unused-vars - if (this.context) { - this.context.save(); - this.context.font = this.fontByStyle(style); - const w = this.context.measureText(text).width; - this.context.restore(); - return w; - } else - return 0; + return text.length*20; }; }