四零语境前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
10 KiB

  1. <template>
  2. <view class="yingbing-text-reader-computed">
  3. <text class="computed-text computed-text-chinese" ref="computedTextChinese" :style="{
  4. 'font-family': fontFamily
  5. }"></text>
  6. <text space="nbsp" class="computed-text computed-text-space" ref="computedTextSpace" :style="{
  7. 'font-family': fontFamily
  8. }"> </text>
  9. <text class="computed-text computed-text-lower" ref="computedTextLower" :style="{
  10. 'font-family': fontFamily
  11. }">a</text>
  12. <text class="computed-text computed-text-upper" ref="computedTextUpper" :style="{
  13. 'font-family': fontFamily
  14. }">A</text>
  15. <text class="computed-text computed-text-number" ref="computedTextNumber" :style="{
  16. 'font-family': fontFamily
  17. }">9</text>
  18. <text class="computed-text computed-text-special" ref="computedTextSpecial" :style="{
  19. 'font-family': fontFamily
  20. }">&</text>
  21. <text class="computed-text computed-text-tibetan" ref="computedTextTibetan" :style="{
  22. 'font-family': fontFamily
  23. }">སྤྱོ</text>
  24. </view>
  25. </template>
  26. <script>
  27. export default {
  28. inject: ['getMeasureSize', 'getCharSize', 'getFontSize', 'getFontFamily', 'getLineGap', 'getTopGap', 'getBottomGap', 'getSlide', 'getHeaderShow', 'getFooterShow', 'getTotalChapter', 'getSplit', 'getPageType'],
  29. props: {
  30. windowHeight: {
  31. type: [Number, String],
  32. default: 0
  33. },
  34. windowWidth: {
  35. type: [Number, String],
  36. default: 0
  37. }
  38. },
  39. computed: {
  40. measureSize () {
  41. return this.getMeasureSize()
  42. },
  43. charSize () {
  44. return this.getCharSize()
  45. },
  46. fontSize () {
  47. return this.getFontSize()
  48. },
  49. fontFamily () {
  50. return this.getFontFamily()
  51. },
  52. lineGap () {
  53. return this.getLineGap()
  54. },
  55. topGap () {
  56. return this.getTopGap()
  57. },
  58. bottomGap () {
  59. return this.getBottomGap()
  60. },
  61. slide () {
  62. return this.getSlide()
  63. },
  64. totalChapter () {
  65. return this.getTotalChapter()
  66. },
  67. split () {
  68. return this.getSplit()
  69. },
  70. pageType () {
  71. return this.getPageType()
  72. },
  73. //展示头部
  74. headerShow () {
  75. return this.pageType == 'scroll' ? this.getHeaderShow() : typeof this.chapter.headerShow == 'boolean' ? this.chapter.headerShow : this.getHeaderShow()//判断是否显示头部
  76. },
  77. //展示底部
  78. footerShow () {
  79. return this.pageType == 'scroll' ? this.getFooterShow() : typeof this.chapter.footerShow == 'boolean' ? this.chapter.footerShow : this.getFooterShow()//判断是否显示头部
  80. },
  81. contentWidth () {
  82. return this.windowWidth - (2 * this.slide)
  83. },
  84. contentHeight () {
  85. return this.windowHeight - this.topGap - this.bottomGap - (this.headerShow ? 30 : 0) - (this.footerShow ? 30 : 0)
  86. }
  87. },
  88. data () {
  89. return {
  90. pages: [],//渲染页面数组
  91. chapter: {},//章节内容临时存储
  92. contents: [],//内容转化数组
  93. success: null,//成功回调
  94. fail: null,//失败回调,
  95. chineseSize: 0,//中文字符尺寸
  96. tibetanSize: 0,//藏文尺寸
  97. spaceSize: 0,//空格尺寸
  98. lowerSize: 0,//小写英文尺寸
  99. upperSize: 0,//大写英文尺寸
  100. numberSize: 0,//数字尺寸
  101. specialSize: 0//特殊字符尺寸
  102. }
  103. },
  104. methods: {
  105. async start ({chapter, success, fail}) {
  106. await this.getComputedTextSize()
  107. this.chapter = chapter//记录章节内容
  108. this.success = success
  109. this.fail = fail
  110. if ( !this.chapter.content ) this.handleSuccess()
  111. else {
  112. const content = this.chapter.content.replace(/\t/g, ' ').replace(/ /g, ' ')
  113. this.contents = this.contentToArr(content)//初始化内容,并将文本内容转为数组
  114. this.computedPage()
  115. }
  116. },
  117. //计算页面
  118. computedPage () {
  119. const start = this.pages.length > 0 ? this.pages[this.pages.length-1].end : 0//获取字符开始位置
  120. //新增页面
  121. const page = this.computedNextPage(start)//计算页面
  122. this.pages.push( Object.assign({}, this.chapter, {
  123. content: page.text.join(''),
  124. contents: page.text,
  125. type: 'text',
  126. start: start,
  127. end: page.end,
  128. }) )
  129. if ( page.end < this.contents.length - 1 ) this.computedPage()
  130. else this.handleSuccess()
  131. },
  132. computedNextPage (start) {
  133. const contentSync = this.contents.slice(start), text = []
  134. let pageHeight = this.fontSize + this.lineGap, length = 0, lastIndex = 0, end = 0
  135. while ( pageHeight <= this.contentHeight ) {
  136. text.push('');
  137. let lineWidth = 0
  138. for ( let i = lastIndex; i < contentSync.length; i++ ) {
  139. const char = contentSync[i]
  140. lineWidth += this.measureText(char, this.fontSize)
  141. if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) {
  142. length += 1
  143. end = start + length;
  144. lastIndex = i + 1;
  145. break;
  146. } else if ( lineWidth >= this.contentWidth ) {
  147. lastIndex = i;
  148. break;
  149. } else {
  150. text[text.length - 1] += char
  151. length += 1;
  152. end = start + length;
  153. }
  154. }
  155. pageHeight += this.fontSize + this.lineGap
  156. if ( end >= contentSync.length - 1 + start ) break;
  157. }
  158. return { start, end, text }
  159. },
  160. //成功回调
  161. handleSuccess () {
  162. const slots1 = this.chapter.frontSlots || []//获取章节前置插槽
  163. const slots2 = this.chapter.backSlots || []//获取章节后置插槽
  164. slots1.reverse()//反转前置插槽
  165. //插入前置插槽
  166. slots1.forEach(name => {
  167. const start = this.pages.length > 0 ? this.pages[0].start : 2
  168. this.pages.unshift(Object.assign({}, this.chapter, {
  169. type: 'slot',
  170. content: name,
  171. start: start - 2,
  172. end: start - 1,
  173. }))
  174. })
  175. //插入后置插槽
  176. slots2.forEach(name => {
  177. const end = this.pages.length > 0 ? this.pages[this.pages.length - 1].end : -1
  178. this.pages.push(Object.assign({}, this.chapter, {
  179. type: 'slot',
  180. content: name,
  181. start: end + 1,
  182. end: end + 2,
  183. }))
  184. })
  185. this.pages = this.pages.map((p, key) => {
  186. const total = this.pages.length
  187. const current = key + 1
  188. const rate = 1 / this.totalChapter
  189. const progress = this.totalChapter ? (rate * (current / total)) + ((p.index - 1) * rate) : 0
  190. return Object.assign({}, p, {total: total, current: current, progress: progress * 100})
  191. })
  192. this.success && this.success(this.pages)
  193. this.success = null
  194. this.fail = null
  195. this.pages = []
  196. this.contents = []
  197. this.chapter = {}
  198. },
  199. //将文本内容转为数组
  200. contentToArr (content) {
  201. const arr = this.split ? [] : content.split('')
  202. if ( arr.length == 0 ) {//如果传入了分隔符
  203. let chars = ''//临时字符串
  204. for ( let i = 0; i < content.length; i++ ) {
  205. const char = content.charAt(i)
  206. if ( /\r|\n/.test(char) ) {//如果是换行符
  207. if ( chars ) arr.push(chars)//直接将先前存储的字符push进数组
  208. arr.push(char)//再将标签push进数组
  209. chars = ''//清空临时字符串
  210. } else if ( this.split.indexOf(char) > -1 ) {//如果是分隔符
  211. chars += char//将分隔符加入字符串
  212. arr.push(chars)//将字符串push进数组
  213. chars = ''//清空临时字符串
  214. } else {//其余字符先存着
  215. chars += char
  216. }
  217. }
  218. }
  219. return arr
  220. },
  221. measureText (text, fontSize=10) {
  222. text = new String(text);
  223. text = text.split('');
  224. let width = 0;
  225. text.forEach((item) => {
  226. const index = this.charSize.findIndex(char => char.text.indexOf(item) > -1)
  227. if ( index > -1 ) {
  228. width += this.charSize[index].size || 0
  229. } else if (/[a-z]/.test(item)) {
  230. width += this.measureSize.lower || this.lowerSize || 7
  231. } else if ( /[A-Z]/.test(item) ) {
  232. width += this.measureSize.upper || this.upperSize || 7
  233. } else if (/[0-9]/.test(item)) {
  234. width += this.measureSize.number || this.numberSize || 5.5
  235. } else if (/[\u4e00-\u9fa5]/.test(item)) { //中文匹配
  236. width += this.measureSize.chinese || this.chineseSize || 10
  237. } else if (/[\u0f00-\u0fff]/.test(item)) { //藏文匹配
  238. width += this.measureSize.tibetan || this.tibetanSize || 4.5
  239. } else if (/\s/.test(item)) {
  240. width += this.measureSize.space || this.spaceSize || 3.5
  241. } else if (/[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(item)) {
  242. width += this.measureSize.special || this.specialSize || 8
  243. } else {
  244. width += this.measureSize.other || this.chineseSize || 10
  245. }
  246. });
  247. return width * fontSize / 10;
  248. },
  249. async getComputedTextSize (selector, el) {
  250. let arr = []
  251. arr.push(this.getSize('.computed-text-chinese', this.$refs.computedTextChinese))
  252. arr.push(this.getSize('.computed-text-space', this.$refs.computedTextSpace))
  253. arr.push(this.getSize('.computed-text-lower', this.$refs.computedTextLower))
  254. arr.push(this.getSize('.computed-text-upper', this.$refs.computedTextUpper))
  255. arr.push(this.getSize('.computed-text-number', this.$refs.computedTextNumber))
  256. arr.push(this.getSize('.computed-text-special', this.$refs.computedTextSpecial))
  257. arr.push(this.getSize('.computed-text-tibetan', this.$refs.computedTextTibetan))
  258. const ress = await Promise.all(arr)
  259. this.chineseSize = ress[0].width * (10 / 20)
  260. this.spaceSize = ress[1].width * (10 / 20)
  261. this.lowerSize = ress[2].width * (10 / 20)
  262. this.upperSize = ress[3].width * (10 / 20)
  263. this.numberSize = ress[4].width * (10 / 20)
  264. this.specialSize = ress[5].width * (10 / 20)
  265. this.tibetanSize = ress[6].width * (10 / 20)
  266. // console.log('chineseSize', this.chineseSize);
  267. // console.log('spaceSize', this.spaceSize);
  268. // console.log('lowerSize', this.lowerSize);
  269. // console.log('upperSize', this.upperSize);
  270. // console.log('numberSize', this.numberSize);
  271. // console.log('specialSize', this.specialSize);
  272. // console.log('tibetanSize', this.tibetanSize);
  273. },
  274. getSize (selector, el) {
  275. return new Promise(resolve => {
  276. // #ifndef APP-NVUE
  277. uni.createSelectorQuery().in(this).select(selector).boundingClientRect(data => {
  278. resolve(data)
  279. }).exec();
  280. // #endif
  281. // #ifdef APP-NVUE
  282. uni.requireNativePlugin('dom').getComponentRect(el, res => {
  283. resolve(res.size)
  284. })
  285. // #endif
  286. })
  287. }
  288. }
  289. }
  290. </script>
  291. <style scoped>
  292. .yingbing-text-reader-computed {
  293. position: absolute;
  294. top: -1000px;
  295. left: 0;
  296. /* #ifndef APP-NVUE */
  297. display: flex;
  298. /* #endif */
  299. flex-direction: row;
  300. flex-wrap: wrap;
  301. visibility: hidden;
  302. }
  303. .yingbing-text-reader-computed .computed-text {
  304. font-size: 20px;
  305. flex-shrink: 0;
  306. }
  307. </style>