mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 完成AIPPT文本替换逻辑
This commit is contained in:
parent
349f101c9e
commit
b107a3105e
@ -1,17 +1,21 @@
|
||||
import api from '@/services'
|
||||
import { nanoid } from 'nanoid'
|
||||
import type { PPTTextElement, Slide, TextType } from '@/types/slides'
|
||||
import type { PPTElement, PPTShapeElement, PPTTextElement, Slide, TextType } from '@/types/slides'
|
||||
import type { AIPPTSlide } from '@/types/AIPPT'
|
||||
import { useSlidesStore } from '@/store'
|
||||
import useAddSlidesOrElements from './useAddSlidesOrElements'
|
||||
import useSlideHandler from './useSlideHandler'
|
||||
|
||||
const findClosestGreaterThanN = (templates: Slide[], n: number, type: TextType) => {
|
||||
const checkTextType = (el: PPTElement, type: TextType) => {
|
||||
return (el.type === 'text' && el.textType === type) || (el.type === 'shape' && el.text && el.text.type === type)
|
||||
}
|
||||
|
||||
const getUseableTemplates = (templates: Slide[], n: number, type: TextType) => {
|
||||
if (n === 1) {
|
||||
const list = templates.filter(slide => {
|
||||
const items = slide.elements.filter(el => el.type === 'text' && el.textType === type)
|
||||
const titles = slide.elements.filter(el => el.type === 'text' && el.textType === 'title')
|
||||
const texts = slide.elements.filter(el => el.type === 'text' && el.textType === 'content')
|
||||
const items = slide.elements.filter(el => checkTextType(el, type))
|
||||
const titles = slide.elements.filter(el => checkTextType(el, 'title'))
|
||||
const texts = slide.elements.filter(el => checkTextType(el, 'content'))
|
||||
|
||||
return !items.length && titles.length === 1 && texts.length === 1
|
||||
})
|
||||
@ -22,33 +26,33 @@ const findClosestGreaterThanN = (templates: Slide[], n: number, type: TextType)
|
||||
let target: Slide | null = null
|
||||
|
||||
const list = templates.filter(slide => {
|
||||
const len = slide.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const len = slide.elements.filter(el => checkTextType(el, type)).length
|
||||
return len >= n
|
||||
})
|
||||
if (list.length === 0) {
|
||||
const sorted = templates.sort((a, b) => {
|
||||
const aLen = a.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const bLen = b.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const aLen = a.elements.filter(el => checkTextType(el, type)).length
|
||||
const bLen = b.elements.filter(el => checkTextType(el, type)).length
|
||||
return aLen - bLen
|
||||
})
|
||||
target = sorted[sorted.length - 1]
|
||||
}
|
||||
else {
|
||||
target = list.reduce((closest, current) => {
|
||||
const currentLen = current.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const closestLen = closest.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const currentLen = current.elements.filter(el => checkTextType(el, type)).length
|
||||
const closestLen = closest.elements.filter(el => checkTextType(el, type)).length
|
||||
return (currentLen - n) <= (closestLen - n) ? current : closest
|
||||
})
|
||||
}
|
||||
|
||||
return templates.filter(slide => {
|
||||
const len = slide.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const targetLen = target!.elements.filter(el => el.type === 'text' && el.textType === type).length
|
||||
const len = slide.elements.filter(el => checkTextType(el, type)).length
|
||||
const targetLen = target!.elements.filter(el => checkTextType(el, type)).length
|
||||
return len === targetLen
|
||||
})
|
||||
}
|
||||
|
||||
const getFontsizeInBox = ({
|
||||
const getAdaptedFontsize = ({
|
||||
text,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@ -99,22 +103,26 @@ const getFontInfo = (htmlString: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getNewTextElData = ({
|
||||
const getNewTextElement = ({
|
||||
el,
|
||||
text,
|
||||
maxLine,
|
||||
longestText,
|
||||
digitPadding,
|
||||
}: {
|
||||
el: PPTTextElement
|
||||
el: PPTTextElement | PPTShapeElement
|
||||
text: string
|
||||
maxLine: number
|
||||
longestText?: string
|
||||
}) => {
|
||||
digitPadding?: boolean
|
||||
}): PPTTextElement | PPTShapeElement => {
|
||||
const padding = 10
|
||||
const width = el.width - padding * 2 - 10
|
||||
|
||||
const fontInfo = getFontInfo(el.content)
|
||||
const size = getFontsizeInBox({
|
||||
let content = el.type === 'text' ? el.content : el.text!.content
|
||||
|
||||
const fontInfo = getFontInfo(content)
|
||||
const size = getAdaptedFontsize({
|
||||
text: longestText || text,
|
||||
fontSize: fontInfo.fontSize,
|
||||
fontFamily: fontInfo.fontFamily,
|
||||
@ -122,15 +130,18 @@ const getNewTextElData = ({
|
||||
maxLine,
|
||||
})
|
||||
|
||||
let content = el.content
|
||||
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, 'text/html')
|
||||
|
||||
const treeWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
|
||||
|
||||
const firstTextNode = treeWalker.nextNode()
|
||||
if (firstTextNode) firstTextNode.textContent = text
|
||||
if (firstTextNode) {
|
||||
if (digitPadding && firstTextNode.textContent && firstTextNode.textContent.length === 2 && text.length === 1) {
|
||||
firstTextNode.textContent = '0' + text
|
||||
}
|
||||
else firstTextNode.textContent = text
|
||||
}
|
||||
|
||||
if (doc.body.innerHTML.indexOf('font-size') === -1) {
|
||||
const p = doc.querySelector('p')
|
||||
@ -139,7 +150,7 @@ const getNewTextElData = ({
|
||||
|
||||
content = doc.body.innerHTML.replace(/font-size:(.+?)px/g, `font-size: ${size}px`)
|
||||
|
||||
return { ...el, content }
|
||||
return el.type === 'text' ? { ...el, content } : { ...el, text: { ...el.text!, content } }
|
||||
}
|
||||
|
||||
export default () => {
|
||||
@ -151,7 +162,7 @@ export default () => {
|
||||
const templateSlides: Slide[] = await api.getMockData('template').then(ret => ret.slides)
|
||||
const _AISlides: AIPPTSlide[] = await api.getMockData('AIPPT')
|
||||
|
||||
const AISlides = []
|
||||
const AISlides: AIPPTSlide[] = []
|
||||
for (const template of _AISlides) {
|
||||
if (template.type === 'content') {
|
||||
const items = template.data.items
|
||||
@ -159,29 +170,63 @@ export default () => {
|
||||
const items1 = items.slice(0, 3)
|
||||
const items2 = items.slice(3)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 3 })
|
||||
}
|
||||
else if (items.length === 7 || items.length === 8) {
|
||||
const items1 = items.slice(0, 4)
|
||||
const items2 = items.slice(4)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 4 })
|
||||
}
|
||||
else if (items.length === 9 || items.length === 10) {
|
||||
const items1 = items.slice(0, 3)
|
||||
const items2 = items.slice(3, 6)
|
||||
const items3 = items.slice(6)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 3 })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 6 })
|
||||
}
|
||||
else if (items.length > 10) {
|
||||
const items1 = items.slice(0, 4)
|
||||
const items2 = items.slice(4, 8)
|
||||
const items3 = items.slice(8)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 4 })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 8 })
|
||||
}
|
||||
else {
|
||||
AISlides.push(template)
|
||||
}
|
||||
}
|
||||
else if (template.type === 'contents') {
|
||||
const items = template.data.items
|
||||
if (items.length === 7) {
|
||||
const items1 = items.slice(0, 5)
|
||||
const items2 = items.slice(5)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 5 })
|
||||
}
|
||||
else if (items.length > 7 && items.length <= 12) {
|
||||
const items1 = items.slice(0, 6)
|
||||
const items2 = items.slice(6)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 })
|
||||
}
|
||||
else if (items.length === 13) {
|
||||
const items1 = items.slice(0, 6)
|
||||
const items2 = items.slice(6, 11)
|
||||
const items3 = items.slice(11)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 11 })
|
||||
}
|
||||
else if (items.length > 13) {
|
||||
const items1 = items.slice(0, 6)
|
||||
const items2 = items.slice(6, 12)
|
||||
const items3 = items.slice(12)
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items1 } })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items2 }, offset: 6 })
|
||||
AISlides.push({ ...template, data: { ...template.data, items: items3 }, offset: 12 })
|
||||
}
|
||||
else {
|
||||
AISlides.push(template)
|
||||
@ -207,11 +252,12 @@ export default () => {
|
||||
for (const item of AISlides) {
|
||||
if (item.type === 'cover') {
|
||||
const elements = coverTemplate.elements.map(el => {
|
||||
if (el.type === 'text' && el.textType === 'title' && item.data.title) {
|
||||
return getNewTextElData({ el, text: item.data.title, maxLine: 1 })
|
||||
if (el.type !== 'text' && el.type !== 'shape') return el
|
||||
if (checkTextType(el, 'title') && item.data.title) {
|
||||
return getNewTextElement({ el, text: item.data.title, maxLine: 1 })
|
||||
}
|
||||
if (el.type === 'text' && el.textType === 'content' && item.data.text) {
|
||||
return getNewTextElData({ el, text: item.data.text, maxLine: 3 })
|
||||
if (checkTextType(el, 'content') && item.data.text) {
|
||||
return getNewTextElement({ el, text: item.data.text, maxLine: 3 })
|
||||
}
|
||||
return el
|
||||
})
|
||||
@ -222,10 +268,16 @@ export default () => {
|
||||
})
|
||||
}
|
||||
else if (item.type === 'contents') {
|
||||
const _contentsTemplates = findClosestGreaterThanN(contentsTemplates, item.data.items.length, 'item')
|
||||
const _contentsTemplates = getUseableTemplates(contentsTemplates, item.data.items.length, 'item')
|
||||
const contentsTemplate = _contentsTemplates[Math.floor(Math.random() * _contentsTemplates.length)]
|
||||
|
||||
const sortedItemIds = contentsTemplate.elements.filter(el => el.type === 'text' && el.textType === 'item').sort((a, b) => {
|
||||
const sortedItemIds = contentsTemplate.elements.filter(el => checkTextType(el, 'item')).sort((a, b) => {
|
||||
const aIndex = a.left + a.top * 2
|
||||
const bIndex = b.left + b.top * 2
|
||||
return aIndex - bIndex
|
||||
}).map(el => el.id)
|
||||
|
||||
const sortedNumberItemIds = contentsTemplate.elements.filter(el => checkTextType(el, 'itemNumber')).sort((a, b) => {
|
||||
const aIndex = a.left + a.top * 2
|
||||
const bIndex = b.left + b.top * 2
|
||||
return aIndex - bIndex
|
||||
@ -234,10 +286,16 @@ export default () => {
|
||||
const longestText = item.data.items.reduce((longest, current) => current.length > longest.length ? current : longest, '')
|
||||
|
||||
const elements = contentsTemplate.elements.map(el => {
|
||||
if (el.type === 'text' && el.textType === 'item') {
|
||||
if (el.type !== 'text' && el.type !== 'shape') return el
|
||||
if (checkTextType(el, 'item')) {
|
||||
const index = sortedItemIds.findIndex(id => id === el.id)
|
||||
const itemTitle = item.data.items[index]
|
||||
if (itemTitle) return getNewTextElData({ el, text: itemTitle, maxLine: 1, longestText })
|
||||
if (itemTitle) return getNewTextElement({ el, text: itemTitle, maxLine: 1, longestText })
|
||||
}
|
||||
if (checkTextType(el, 'itemNumber')) {
|
||||
const index = sortedNumberItemIds.findIndex(id => id === el.id)
|
||||
const offset = item.offset || 0
|
||||
return getNewTextElement({ el, text: index + offset + 1 + '', maxLine: 1, digitPadding: true })
|
||||
}
|
||||
return el
|
||||
})
|
||||
@ -250,14 +308,15 @@ export default () => {
|
||||
else if (item.type === 'transition') {
|
||||
transitionIndex++
|
||||
const elements = transitionTemplate.elements.map(el => {
|
||||
if (el.type === 'text' && el.textType === 'title' && item.data.title) {
|
||||
return getNewTextElData({ el, text: item.data.title, maxLine: 1 })
|
||||
if (el.type !== 'text' && el.type !== 'shape') return el
|
||||
if (checkTextType(el, 'title') && item.data.title) {
|
||||
return getNewTextElement({ el, text: item.data.title, maxLine: 1 })
|
||||
}
|
||||
if (el.type === 'text' && el.textType === 'content' && item.data.text) {
|
||||
return getNewTextElData({ el, text: item.data.text, maxLine: 3 })
|
||||
if (checkTextType(el, 'content') && item.data.text) {
|
||||
return getNewTextElement({ el, text: item.data.text, maxLine: 3 })
|
||||
}
|
||||
if (el.type === 'text' && el.textType === 'partNumber') {
|
||||
return getNewTextElData({ el, text: transitionIndex + '', maxLine: 1 })
|
||||
if (checkTextType(el, 'partNumber')) {
|
||||
return getNewTextElement({ el, text: transitionIndex + '', maxLine: 1, digitPadding: true })
|
||||
}
|
||||
return el
|
||||
})
|
||||
@ -268,16 +327,22 @@ export default () => {
|
||||
})
|
||||
}
|
||||
else if (item.type === 'content') {
|
||||
const _contentTemplates = findClosestGreaterThanN(contentTemplates, item.data.items.length, 'item')
|
||||
const _contentTemplates = getUseableTemplates(contentTemplates, item.data.items.length, 'item')
|
||||
const contentTemplate = _contentTemplates[Math.floor(Math.random() * _contentTemplates.length)]
|
||||
|
||||
const sortedTitleItemIds = contentTemplate.elements.filter(el => el.type === 'text' && el.textType === 'itemTitle').sort((a, b) => {
|
||||
const sortedTitleItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'itemTitle')).sort((a, b) => {
|
||||
const aIndex = a.left + a.top * 2
|
||||
const bIndex = b.left + b.top * 2
|
||||
return aIndex - bIndex
|
||||
}).map(el => el.id)
|
||||
|
||||
const sortedTextItemIds = contentTemplate.elements.filter(el => el.type === 'text' && el.textType === 'item').sort((a, b) => {
|
||||
const sortedTextItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'item')).sort((a, b) => {
|
||||
const aIndex = a.left + a.top * 2
|
||||
const bIndex = b.left + b.top * 2
|
||||
return aIndex - bIndex
|
||||
}).map(el => el.id)
|
||||
|
||||
const sortedNumberItemIds = contentTemplate.elements.filter(el => checkTextType(el, 'itemNumber')).sort((a, b) => {
|
||||
const aIndex = a.left + a.top * 2
|
||||
const bIndex = b.left + b.top * 2
|
||||
return aIndex - bIndex
|
||||
@ -294,32 +359,36 @@ export default () => {
|
||||
const longestText = itemTexts.reduce((longest, current) => current.length > longest.length ? current : longest, '')
|
||||
|
||||
const elements = contentTemplate.elements.map(el => {
|
||||
if (el.type !== 'text' && el.type !== 'shape') return el
|
||||
if (item.data.items.length === 1) {
|
||||
const contentItem = item.data.items[0]
|
||||
if (el.type === 'text' && el.textType === 'content' && contentItem.text) {
|
||||
return getNewTextElData({ el, text: contentItem.text, maxLine: 6 })
|
||||
if (checkTextType(el, 'content') && contentItem.text) {
|
||||
return getNewTextElement({ el, text: contentItem.text, maxLine: 6 })
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (el.type === 'text' && el.textType === 'itemTitle') {
|
||||
if (checkTextType(el, 'itemTitle')) {
|
||||
const index = sortedTitleItemIds.findIndex(id => id === el.id)
|
||||
const contentItem = item.data.items[index]
|
||||
if (contentItem && contentItem.title) {
|
||||
return getNewTextElData({ el, text: contentItem.title, longestText: longestTitle, maxLine: 1 })
|
||||
return getNewTextElement({ el, text: contentItem.title, longestText: longestTitle, maxLine: 1 })
|
||||
}
|
||||
return { ...el, isInvalid: true }
|
||||
}
|
||||
if (el.type === 'text' && el.textType === 'item') {
|
||||
if (checkTextType(el, 'item')) {
|
||||
const index = sortedTextItemIds.findIndex(id => id === el.id)
|
||||
const contentItem = item.data.items[index]
|
||||
if (contentItem && contentItem.text) {
|
||||
return getNewTextElData({ el, text: contentItem.text, longestText, maxLine: 4 })
|
||||
return getNewTextElement({ el, text: contentItem.text, longestText, maxLine: 4 })
|
||||
}
|
||||
return { ...el, isInvalid: true }
|
||||
}
|
||||
if (checkTextType(el, 'itemNumber')) {
|
||||
const index = sortedNumberItemIds.findIndex(id => id === el.id)
|
||||
const offset = item.offset || 0
|
||||
return getNewTextElement({ el, text: index + offset + 1 + '', maxLine: 1, digitPadding: true })
|
||||
}
|
||||
}
|
||||
if (el.type === 'text' && el.textType === 'title' && item.data.title) {
|
||||
return getNewTextElData({ el, text: item.data.title, maxLine: 1 })
|
||||
if (checkTextType(el, 'title') && item.data.title) {
|
||||
return getNewTextElement({ el, text: item.data.title, maxLine: 1 })
|
||||
}
|
||||
return el
|
||||
})
|
||||
|
@ -11,6 +11,7 @@ export interface AIPPTContents {
|
||||
data: {
|
||||
items: string[]
|
||||
}
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface AIPPTTransition {
|
||||
@ -29,7 +30,8 @@ export interface AIPPTContent {
|
||||
title: string
|
||||
text: string
|
||||
}[]
|
||||
}
|
||||
},
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface AIPPTEnd {
|
||||
|
Loading…
x
Reference in New Issue
Block a user