text.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import { getHorizontalAlign } from './align'
  2. import { getTextByPathList } from './utils'
  3. import {
  4. getFontType,
  5. getFontColor,
  6. getFontSize,
  7. getFontBold,
  8. getFontItalic,
  9. getFontDecoration,
  10. getFontDecorationLine,
  11. getFontSpace,
  12. getFontSubscript,
  13. getFontShadow
  14. } from './fontStyle'
  15. export function genTextBody(
  16. textBodyNode,
  17. spNode,
  18. slideLayoutSpNode,
  19. type,
  20. warpObj
  21. ) {
  22. if (!textBodyNode) return ''
  23. let text = ''
  24. const listCounters = {} // 添加列表计数器对象
  25. const slideMasterTextStyles = warpObj['slideMasterTextStyles']
  26. const pFontStyle = getTextByPathList(spNode, ['p:style', 'a:fontRef'])
  27. const pNode = textBodyNode['a:p']
  28. const pNodes = pNode.constructor === Array ? pNode : [pNode]
  29. let isList = ''
  30. for (const pNode of pNodes) {
  31. let rNode = pNode['a:r']
  32. let fldNode = pNode['a:fld']
  33. let brNode = pNode['a:br']
  34. if (rNode) {
  35. rNode = rNode.constructor === Array ? rNode : [rNode]
  36. if (fldNode) {
  37. fldNode = fldNode.constructor === Array ? fldNode : [fldNode]
  38. rNode = rNode.concat(fldNode)
  39. }
  40. if (brNode) {
  41. brNode = brNode.constructor === Array ? brNode : [brNode]
  42. brNode.forEach((item) => (item.type = 'br'))
  43. if (brNode.length > 1) brNode.shift()
  44. rNode = rNode.concat(brNode)
  45. rNode.sort((a, b) => {
  46. if (!a.attrs || !b.attrs) return true
  47. return a.attrs.order - b.attrs.order
  48. })
  49. }
  50. }
  51. const align = getHorizontalAlign(pNode, spNode, type, warpObj)
  52. const listType = getListType(pNode)
  53. if (listType) {
  54. const [tagName, className] = listType.split(' ')
  55. const level = parseInt(pNode['a:pPr'].attrs?.lvl || '0') // 获取列表层级
  56. const indentSize = (level + 1) * 20 // 根据层级计算缩进大小
  57. if (!isList) {
  58. text += `<${tagName} style="list-style: none; " class="${className}">`
  59. isList = listType
  60. listCounters[className] = 0
  61. }
  62. else if (isList && isList !== listType) {
  63. text += `</${isList.split(' ')[0]}>`
  64. text += `<${tagName} style="list-style: none; " class="${className}">`
  65. isList = listType
  66. listCounters[className] = 0
  67. }
  68. listCounters[className]++
  69. const currentNumber = listCounters[className]
  70. text += `<li style="text-align: ${align}; padding-left: ${indentSize}px;">`
  71. if (className === 'custom-bullet') {
  72. const bulletChar = pNode['a:pPr']['a:buChar'].attrs?.char || '•'
  73. const symbolMap = {
  74. 'n': '■', // 实心方块
  75. 'l': '●', // 实心圆
  76. 'u': '◆', // 实心菱形
  77. 'p': '□', // 空心方块
  78. 'ü': '✔', // 对号
  79. 'Ø': '➢', // 箭头
  80. '': '•' // 默认圆点
  81. }
  82. const displayChar = symbolMap[bulletChar] || bulletChar
  83. // 获取字体大小,与内容保持一致
  84. const fontSize = getFontSize(textBodyNode, slideLayoutSpNode, type, slideMasterTextStyles)
  85. const fontSizeStyle = fontSize ? `font-size: ${fontSize};` : ''
  86. text += `<span style="display: inline-block; width: 20px; ${fontSizeStyle}; margin-left: -20px;">${displayChar}</span>`
  87. }
  88. else {
  89. let displayNumber = ''
  90. switch (className) {
  91. case 'circle-number':
  92. const circleNums = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']
  93. displayNumber = circleNums[currentNumber - 1] || `${currentNumber}`
  94. break
  95. case 'roman-upper':
  96. const romanNums = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
  97. displayNumber = `${romanNums[currentNumber - 1]}.`
  98. break
  99. case 'alpha-upper':
  100. displayNumber = `${String.fromCharCode(64 + currentNumber)}.`
  101. break
  102. case 'alpha-lower-paren':
  103. displayNumber = `${String.fromCharCode(96 + currentNumber)})`
  104. break
  105. case 'alpha-lower':
  106. displayNumber = `${String.fromCharCode(96 + currentNumber)}.`
  107. break
  108. case 'chinese-upper': // 添加中文数字
  109. const chineseUpperNums = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
  110. displayNumber = `${chineseUpperNums[currentNumber - 1]}.`
  111. break
  112. default:
  113. displayNumber = `${currentNumber}.`
  114. }
  115. // 获取字体大小,与内容保持一致
  116. const fontSize = getFontSize(textBodyNode, slideLayoutSpNode, type, slideMasterTextStyles)
  117. const fontSizeStyle = fontSize ? `font-size: ${fontSize};` : ''
  118. text += `<span style="display: inline-block; ${fontSizeStyle}; margin-left: -20px;">${displayNumber}</span>`
  119. }
  120. }
  121. else {
  122. if (isList) {
  123. text += `</${isList.split(' ')[0]}>`
  124. isList = ''
  125. }
  126. text += `<p style="text-align: ${align};">`
  127. }
  128. if (!rNode) {
  129. text += genSpanElement(
  130. pNode,
  131. spNode,
  132. textBodyNode,
  133. pFontStyle,
  134. slideLayoutSpNode,
  135. type,
  136. warpObj
  137. )
  138. }
  139. else {
  140. for (const rNodeItem of rNode) {
  141. text += genSpanElement(
  142. rNodeItem,
  143. pNode,
  144. textBodyNode,
  145. pFontStyle,
  146. slideLayoutSpNode,
  147. type,
  148. warpObj
  149. )
  150. }
  151. }
  152. if (listType) text += '</li>'
  153. else text += '</p>'
  154. }
  155. return text
  156. }
  157. export function getListType(node) {
  158. const pPrNode = node['a:pPr']
  159. if (!pPrNode) return ''
  160. if (pPrNode['a:buChar']) {
  161. return 'ul custom-bullet'
  162. }
  163. if (pPrNode['a:buAutoNum']) {
  164. const numType = pPrNode['a:buAutoNum'].attrs?.type || 'arabicPeriod'
  165. // 根据不同的编号类型返回对应的样式
  166. switch (numType) {
  167. case 'circleNumDbPlain':
  168. return 'ol circle-number' // ①②③
  169. case 'romanUcPeriod':
  170. return 'ol roman-upper' // I. II. III.
  171. case 'alphaUcPeriod':
  172. return 'ol alpha-upper' // A. B. C.
  173. case 'alphaLcParen':
  174. return 'ol alpha-lower-paren' // a) b) c)
  175. case 'alphaLcPeriod':
  176. return 'ol alpha-lower' // a. b. c.
  177. case 'ea1JpnChsDbPeriod':
  178. return 'ol chinese-upper' // 一. 二. 三.
  179. default:
  180. return 'ol decimal' // 1. 2. 3.
  181. }
  182. }
  183. return ''
  184. }
  185. export function genSpanElement(
  186. node,
  187. pNode,
  188. textBodyNode,
  189. pFontStyle,
  190. slideLayoutSpNode,
  191. type,
  192. warpObj
  193. ) {
  194. const lstStyle = textBodyNode['a:lstStyle']
  195. const slideMasterTextStyles = warpObj['slideMasterTextStyles']
  196. let lvl = 1
  197. const pPrNode = pNode['a:pPr']
  198. if (pPrNode) {
  199. const lvlNode = getTextByPathList(pPrNode, ['attrs', 'lvl'])
  200. if (lvlNode !== undefined) lvl = parseInt(lvlNode) + 1
  201. }
  202. let fontSize
  203. const directSize = getTextByPathList(node, ['a:rPr', 'attrs', 'sz'])
  204. if (directSize) {
  205. // 如果节点本身有字体大小设置,直接使用
  206. fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
  207. }
  208. else {
  209. // 如果节点没有字体大小设置,使用 textBodyNode
  210. fontSize = getFontSize(textBodyNode, slideLayoutSpNode, type, slideMasterTextStyles)
  211. }
  212. let text = node['a:t']
  213. if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
  214. if (typeof text !== 'string') text = '&nbsp;'
  215. let styleText = ''
  216. const fontColor = getFontColor(
  217. node,
  218. pNode,
  219. lstStyle,
  220. pFontStyle,
  221. lvl,
  222. warpObj
  223. )
  224. const fontType = getFontType(node, type, warpObj)
  225. const fontBold = getFontBold(node)
  226. const fontItalic = getFontItalic(node)
  227. const fontDecoration = getFontDecoration(node)
  228. const fontDecorationLine = getFontDecorationLine(node)
  229. const fontSpace = getFontSpace(node)
  230. const shadow = getFontShadow(node, warpObj)
  231. const subscript = getFontSubscript(node)
  232. if (fontColor) styleText += `color: ${fontColor};`
  233. if (fontSize) styleText += `font-size: ${fontSize};`
  234. if (fontType) styleText += `font-family: ${fontType};`
  235. if (fontBold) styleText += `font-weight: ${fontBold};`
  236. if (fontItalic) styleText += `font-style: ${fontItalic};`
  237. if (fontDecoration) styleText += `text-decoration: ${fontDecoration};`
  238. if (fontDecorationLine) {
  239. styleText += `text-decoration-line: ${fontDecorationLine};`
  240. }
  241. if (fontSpace) styleText += `letter-spacing: ${fontSpace};`
  242. if (subscript) styleText += `vertical-align: ${subscript};`
  243. if (shadow) styleText += `text-shadow: ${shadow};`
  244. const linkID = getTextByPathList(node, [
  245. 'a:rPr',
  246. 'a:hlinkClick',
  247. 'attrs',
  248. 'r:id'
  249. ])
  250. if (linkID) {
  251. const linkURL = warpObj['slideResObj'][linkID]['target']
  252. return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text
  253. .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
  254. .replace(/\s/g, '&nbsp;')}</a></span>`
  255. }
  256. return `<span style="${styleText}">${text
  257. .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
  258. .replace(/\s/g, '&nbsp;')}</span>`
  259. }