| <template> | |
| 	<view class="yingbing-text-reader-computed"> | |
| 		<text class="computed-text computed-text-chinese" ref="computedTextChinese" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">中</text> | |
| 		<text space="nbsp" class="computed-text computed-text-space" ref="computedTextSpace" :style="{ | |
| 			'font-family': fontFamily | |
| 		}"> </text> | |
| 		<text class="computed-text computed-text-lower" ref="computedTextLower" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">a</text> | |
| 		<text class="computed-text computed-text-upper" ref="computedTextUpper" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">A</text> | |
| 		<text class="computed-text computed-text-number" ref="computedTextNumber" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">9</text> | |
| 		<text class="computed-text computed-text-special" ref="computedTextSpecial" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">&</text> | |
| 		<text class="computed-text computed-text-tibetan" ref="computedTextTibetan" :style="{ | |
| 			'font-family': fontFamily | |
| 		}">སྤྱོ</text> | |
| 	</view> | |
| </template> | |
| 
 | |
| <script> | |
| 	export default { | |
| 		inject: ['getMeasureSize', 'getCharSize', 'getFontSize', 'getFontFamily', 'getLineGap', 'getTopGap', 'getBottomGap', 'getSlide', 'getHeaderShow', 'getFooterShow', 'getTotalChapter', 'getSplit', 'getPageType'], | |
| 		props: { | |
| 			windowHeight: { | |
| 				type: [Number, String], | |
| 				default: 0 | |
| 			}, | |
| 			windowWidth: { | |
| 				type: [Number, String], | |
| 				default: 0 | |
| 			} | |
| 		}, | |
| 		computed: { | |
| 			measureSize () { | |
| 				return this.getMeasureSize() | |
| 			}, | |
| 			charSize () { | |
| 				return this.getCharSize() | |
| 			}, | |
| 			fontSize () { | |
| 				return this.getFontSize() | |
| 			}, | |
| 			fontFamily () { | |
| 				return this.getFontFamily() | |
| 			}, | |
| 			lineGap () { | |
| 				return this.getLineGap() | |
| 			}, | |
| 			topGap () { | |
| 				return this.getTopGap() | |
| 			}, | |
| 			bottomGap () { | |
| 				return this.getBottomGap() | |
| 			}, | |
| 			slide () { | |
| 				return this.getSlide() | |
| 			}, | |
| 			totalChapter () { | |
| 				return this.getTotalChapter() | |
| 			}, | |
| 			split () { | |
| 				return this.getSplit() | |
| 			}, | |
| 			pageType () { | |
| 				return this.getPageType() | |
| 			}, | |
| 			//展示头部 | |
| 			headerShow () { | |
| 				return this.pageType == 'scroll' ? this.getHeaderShow() : typeof this.chapter.headerShow == 'boolean' ? this.chapter.headerShow : this.getHeaderShow()//判断是否显示头部 | |
| 			}, | |
| 			//展示底部 | |
| 			footerShow () { | |
| 				return this.pageType == 'scroll' ? this.getFooterShow() : typeof this.chapter.footerShow == 'boolean' ? this.chapter.footerShow : this.getFooterShow()//判断是否显示头部 | |
| 			}, | |
| 			contentWidth () { | |
| 				return this.windowWidth - (2 * this.slide) | |
| 			}, | |
| 			contentHeight () { | |
| 				return this.windowHeight - this.topGap - this.bottomGap - (this.headerShow ? 30 : 0) - (this.footerShow ? 30 : 0) | |
| 			} | |
| 		}, | |
| 		data () { | |
| 			return { | |
| 				pages: [],//渲染页面数组 | |
| 				chapter: {},//章节内容临时存储 | |
| 				contents: [],//内容转化数组 | |
| 				success: null,//成功回调 | |
| 				fail: null,//失败回调, | |
| 				chineseSize: 0,//中文字符尺寸 | |
| 				tibetanSize: 0,//藏文尺寸 | |
| 				spaceSize: 0,//空格尺寸 | |
| 				lowerSize: 0,//小写英文尺寸 | |
| 				upperSize: 0,//大写英文尺寸 | |
| 				numberSize: 0,//数字尺寸 | |
| 				specialSize: 0//特殊字符尺寸 | |
| 			} | |
| 		}, | |
| 		methods: { | |
| 			async start ({chapter, success, fail}) { | |
| 				await this.getComputedTextSize() | |
| 				this.chapter = chapter//记录章节内容 | |
| 				this.success = success | |
| 				this.fail = fail | |
| 				if ( !this.chapter.content ) this.handleSuccess() | |
| 				else { | |
| 					const content = this.chapter.content.replace(/\t/g, ' ').replace(/ /g, ' ') | |
| 					this.contents = this.contentToArr(content)//初始化内容,并将文本内容转为数组 | |
| 					this.computedPage() | |
| 				} | |
| 			}, | |
| 			//计算页面 | |
| 			computedPage () { | |
| 				const start = this.pages.length > 0 ? this.pages[this.pages.length-1].end : 0//获取字符开始位置 | |
| 				//新增页面 | |
| 				const page = this.computedNextPage(start)//计算页面 | |
| 				this.pages.push( Object.assign({}, this.chapter, { | |
| 					content: page.text.join(''), | |
| 					contents: page.text, | |
| 					type: 'text', | |
| 					start: start, | |
| 					end: page.end, | |
| 				}) ) | |
| 				if ( page.end < this.contents.length - 1 ) this.computedPage() | |
| 				else this.handleSuccess() | |
| 			}, | |
| 			computedNextPage (start) { | |
| 				const contentSync = this.contents.slice(start), text = [] | |
| 				let pageHeight = this.fontSize + this.lineGap, length = 0, lastIndex = 0, end = 0 | |
| 				while ( pageHeight <= this.contentHeight ) { | |
| 					text.push(''); | |
| 					let lineWidth = 0 | |
| 					for ( let i = lastIndex; i < contentSync.length; i++ ) { | |
| 						const char = contentSync[i] | |
| 						lineWidth += this.measureText(char, this.fontSize) | |
| 						if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) { | |
| 							length += 1 | |
| 							end = start + length; | |
| 							lastIndex = i + 1; | |
| 							break; | |
| 						} else if ( lineWidth >= this.contentWidth ) { | |
| 							lastIndex = i; | |
| 							break; | |
| 						} else { | |
| 							text[text.length - 1] += char | |
| 							length += 1; | |
| 							end = start + length; | |
| 						} | |
| 					} | |
| 					pageHeight += this.fontSize + this.lineGap | |
| 					if ( end >= contentSync.length - 1 + start ) break; | |
| 				} | |
| 				return { start, end, text } | |
| 			}, | |
| 			//成功回调 | |
| 			handleSuccess () { | |
| 				const slots1 = this.chapter.frontSlots || []//获取章节前置插槽 | |
| 				const slots2 = this.chapter.backSlots || []//获取章节后置插槽 | |
| 				slots1.reverse()//反转前置插槽 | |
| 				//插入前置插槽 | |
| 				slots1.forEach(name => { | |
| 					const start = this.pages.length > 0 ? this.pages[0].start : 2 | |
| 					this.pages.unshift(Object.assign({}, this.chapter, { | |
| 						type: 'slot', | |
| 						content: name, | |
| 						start: start - 2, | |
| 						end: start - 1, | |
| 					})) | |
| 				}) | |
| 				//插入后置插槽 | |
| 				slots2.forEach(name => { | |
| 					const end = this.pages.length > 0 ? this.pages[this.pages.length - 1].end : -1 | |
| 					this.pages.push(Object.assign({}, this.chapter, { | |
| 						type: 'slot', | |
| 						content: name, | |
| 						start: end + 1, | |
| 						end: end + 2, | |
| 					})) | |
| 				}) | |
| 				this.pages = this.pages.map((p, key) => { | |
| 					const total = this.pages.length | |
| 					const current = key + 1 | |
| 					const rate = 1 / this.totalChapter | |
| 					const progress = this.totalChapter ? (rate * (current / total)) + ((p.index - 1) * rate) : 0 | |
| 					return Object.assign({}, p, {total: total, current: current, progress: progress * 100}) | |
| 				}) | |
| 				this.success && this.success(this.pages) | |
| 				this.success = null | |
| 				this.fail = null | |
| 				this.pages = [] | |
| 				this.contents = [] | |
| 				this.chapter = {} | |
| 			}, | |
| 			//将文本内容转为数组 | |
| 			contentToArr (content) { | |
| 				const arr = this.split ? [] : content.split('') | |
| 				if ( arr.length == 0 ) {//如果传入了分隔符 | |
| 					let chars = ''//临时字符串 | |
| 					for ( let i = 0; i < content.length; i++ ) { | |
| 						const char = content.charAt(i) | |
| 						if ( /\r|\n/.test(char) ) {//如果是换行符 | |
| 							if ( chars ) arr.push(chars)//直接将先前存储的字符push进数组 | |
| 							arr.push(char)//再将标签push进数组 | |
| 							chars = ''//清空临时字符串 | |
| 						} else if ( this.split.indexOf(char) > -1 ) {//如果是分隔符 | |
| 							chars += char//将分隔符加入字符串 | |
| 							arr.push(chars)//将字符串push进数组 | |
| 							chars = ''//清空临时字符串 | |
| 						} else {//其余字符先存着 | |
| 							chars += char | |
| 						} | |
| 					} | |
| 				} | |
| 				return arr | |
| 			}, | |
| 			measureText (text, fontSize=10) { | |
| 			  text = new String(text); | |
| 			  text = text.split(''); | |
| 			  let width = 0; | |
| 			  text.forEach((item) => { | |
| 				const index = this.charSize.findIndex(char => char.text.indexOf(item) > -1) | |
| 				if ( index > -1 ) { | |
| 				  width += this.charSize[index].size || 0 | |
| 				} else if (/[a-z]/.test(item)) { | |
| 			      width += this.measureSize.lower || this.lowerSize || 7 | |
| 			    } else if ( /[A-Z]/.test(item) ) { | |
| 				  width += this.measureSize.upper || this.upperSize || 7 | |
| 				} else if (/[0-9]/.test(item)) { | |
| 			      width += this.measureSize.number || this.numberSize || 5.5 | |
| 			    } else if (/[\u4e00-\u9fa5]/.test(item)) { //中文匹配 | |
| 			      width += this.measureSize.chinese || this.chineseSize || 10 | |
| 			    } else if (/[\u0f00-\u0fff]/.test(item)) { //藏文匹配 | |
| 			      width += this.measureSize.tibetan || this.tibetanSize || 4.5 | |
| 			    } else if (/\s/.test(item)) { | |
| 			      width += this.measureSize.space || this.spaceSize || 3.5 | |
| 			    } else if (/[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(item)) { | |
| 			      width += this.measureSize.special || this.specialSize || 8 | |
| 			    } else { | |
| 			      width += this.measureSize.other || this.chineseSize || 10 | |
| 			    } | |
| 			  }); | |
| 			  return width * fontSize / 10; | |
| 			}, | |
| 			async getComputedTextSize (selector, el) { | |
| 				let arr = [] | |
| 				arr.push(this.getSize('.computed-text-chinese', this.$refs.computedTextChinese)) | |
| 				arr.push(this.getSize('.computed-text-space', this.$refs.computedTextSpace)) | |
| 				arr.push(this.getSize('.computed-text-lower', this.$refs.computedTextLower)) | |
| 				arr.push(this.getSize('.computed-text-upper', this.$refs.computedTextUpper)) | |
| 				arr.push(this.getSize('.computed-text-number', this.$refs.computedTextNumber)) | |
| 				arr.push(this.getSize('.computed-text-special', this.$refs.computedTextSpecial)) | |
| 				arr.push(this.getSize('.computed-text-tibetan', this.$refs.computedTextTibetan)) | |
| 				const ress = await Promise.all(arr) | |
| 				this.chineseSize = ress[0].width * (10 / 20) | |
| 				this.spaceSize = ress[1].width * (10 / 20) | |
| 				this.lowerSize = ress[2].width * (10 / 20) | |
| 				this.upperSize = ress[3].width * (10 / 20) | |
| 				this.numberSize = ress[4].width * (10 / 20) | |
| 				this.specialSize = ress[5].width * (10 / 20) | |
| 				this.tibetanSize = ress[6].width * (10 / 20) | |
| 				// console.log('chineseSize', this.chineseSize); | |
| 				// console.log('spaceSize', this.spaceSize); | |
| 				// console.log('lowerSize', this.lowerSize); | |
| 				// console.log('upperSize', this.upperSize); | |
| 				// console.log('numberSize', this.numberSize); | |
| 				// console.log('specialSize', this.specialSize); | |
| 				// console.log('tibetanSize', this.tibetanSize); | |
| 			}, | |
| 			getSize (selector, el) { | |
| 				return new Promise(resolve => { | |
| 					// #ifndef APP-NVUE | |
| 					uni.createSelectorQuery().in(this).select(selector).boundingClientRect(data => { | |
| 						resolve(data) | |
| 					}).exec(); | |
| 					// #endif | |
| 					// #ifdef APP-NVUE | |
| 					uni.requireNativePlugin('dom').getComponentRect(el, res => { | |
| 						resolve(res.size) | |
| 					}) | |
| 					// #endif | |
| 				}) | |
| 			} | |
| 		} | |
| 	} | |
| </script> | |
| 
 | |
| <style scoped> | |
| 	.yingbing-text-reader-computed { | |
| 		position: absolute; | |
| 		top: -1000px; | |
| 		left: 0; | |
| 		/* #ifndef APP-NVUE */ | |
| 		display: flex; | |
| 		/* #endif */ | |
| 		flex-direction: row; | |
| 		flex-wrap: wrap; | |
| 		visibility: hidden; | |
| 	} | |
| 	.yingbing-text-reader-computed .computed-text { | |
| 		font-size: 20px; | |
| 		flex-shrink: 0; | |
| 	} | |
| </style> |