Forráskód Böngészése

add: 添加、选中图片;属性面板

wzl 1 éve
szülő
commit
289cb5a0ad
25 módosított fájl, 2133 hozzáadás és 76 törlés
  1. 290 0
      packages/core/src/editor/add_image.js
  2. 120 25
      packages/core/src/editor/content_container.js
  3. 1415 0
      packages/core/src/editor/image_editor.js
  4. 4 4
      packages/core/src/editor/text_editor.js
  5. 6 2
      packages/core/src/index.js
  6. 52 6
      packages/core/src/worker/compdfkit_worker.js
  7. BIN
      packages/webview/public/example/Number Pages.pdf
  8. BIN
      packages/webview/public/example/PDF Tech Admin Console Guidelines.pdf
  9. 1 1
      packages/webview/src/components/App/index.vue
  10. 181 11
      packages/webview/src/components/EditTextPanel/EditTextPanel.vue
  11. 4 4
      packages/webview/src/components/EditToolbar/EditToolBar.vue
  12. 12 10
      packages/webview/src/components/DocumentContainer/DocumentContainer.vue
  13. 5 0
      packages/webview/src/components/Icon/DownloadImage.vue
  14. 5 0
      packages/webview/src/components/Icon/FlipX.vue
  15. 5 0
      packages/webview/src/components/Icon/FlipY.vue
  16. 5 0
      packages/webview/src/components/Icon/ReplaceImage.vue
  17. 5 0
      packages/webview/src/components/Icon/RotateImageLeft.vue
  18. 5 0
      packages/webview/src/components/Icon/RotateImageRight.vue
  19. 5 0
      packages/webview/src/components/Icon/TailorImage.vue
  20. 2 2
      packages/webview/src/components/ToggleRightPanelButton/index.vue
  21. 2 2
      packages/webview/src/components/Toolbar/Toolbar.vue
  22. 2 2
      packages/webview/src/core/index.js
  23. 3 0
      packages/webview/src/core/setContentEditorProperty.js
  24. 0 3
      packages/webview/src/core/setTextProperty.js
  25. 4 4
      packages/webview/src/stores/modules/viewer.js

+ 290 - 0
packages/core/src/editor/add_image.js

@@ -0,0 +1,290 @@
+import {
+  getAbsoluteCoordinate,
+  createElement,
+  getInitialPoint,
+  getSvgPathFromStroke
+} from '../annotation/utils'
+
+export default class AddImage {
+  constructor ({
+    tool,
+    color,
+    container,
+    viewport,
+    page,
+    eventBus,
+    contentContainer
+  }) {
+    this._tool = tool
+    this.color = color
+    this.container = container
+    this.viewport = viewport
+    this.page = page
+    this.eventBus = eventBus
+    this.contentContainer = contentContainer
+
+    this.path = []
+    this.initStartPoint = null
+    this.initEndPoint = null
+    this.shapeEle = null
+
+    this.onMousedown = this.handleMouseDown.bind(this)
+    this.onMouseup = this.handleMouseUp.bind(this)
+    this.onMousemove = this.handleMouseMove.bind(this)
+
+    this.init()
+  }
+
+  init () {
+    this.container.addEventListener('mousedown', this.onMousedown)
+    this.container.addEventListener('touchstart', this.onMousedown)
+    this.container.style.cursor = 'crosshair'
+    this.container.style.touchAction = 'none'
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+  }
+
+  get tool () {
+    return this._tool
+  }
+
+  set tool (toolType) {
+    if (toolType === this._tool) return
+    if (!toolType) {
+      this.reset()
+      this.container.style.cursor = 'default'
+      this.container.style.touchAction = ''
+
+      this.container.removeEventListener('mousedown', this.onMousedown)
+      this.container.removeEventListener('touchstart', this.onMousedown)
+    }
+    this._tool = toolType
+  }
+
+  reset () {
+    this.initStartPoint = null
+    this.initEndPoint = null
+    if (this.shapeEle) this.shapeEle.remove()
+    this.shapeEle = null
+    this.path = []
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+
+    this.container.removeEventListener('mousedown', this.onMousedown)
+    this.container.removeEventListener('touchstart', this.onMousedown)
+  }
+
+  handleMouseDown (event) {
+    if ((!event.target.className && !event.target.className.includes('contentContainer')) || this.contentContainer.selectedFrameIndex !== -1) return
+    const { x, y } = getAbsoluteCoordinate(this.container, event)
+    this.initStartPoint = { x, y }
+
+    document.addEventListener('mousemove', this.onMousemove)
+    document.addEventListener('mouseup', this.onMouseup)
+    
+    document.addEventListener('touchmove', this.onMousemove)
+    document.addEventListener('touchend', this.onMouseup)
+  }
+
+  handleMouseUp () {
+    let initStartPoint = this.initStartPoint
+    let initEndPoint = this.initEndPoint
+    if (initEndPoint && initStartPoint.x !== initEndPoint.x && initStartPoint.y !== initEndPoint.y) {
+      let start = getInitialPoint(initStartPoint, this.viewport, this.viewport.scale)
+      let end = getInitialPoint(initEndPoint, this.viewport, this.viewport.scale)
+      
+      const newStart = {
+        x: Math.min(start.x, end.x),
+        y: Math.min(start.y, end.y)
+      }
+      const newEnd = {
+        x: Math.max(start.x, end.x),
+        y: Math.max(start.y, end.y)
+      }
+      
+      const rect = {
+        left: newStart.x,
+        top: newStart.y,
+        right: newEnd.x,
+        bottom: newStart.y,
+        width: newEnd.x - newStart.x,
+        height: newEnd.y - newStart.y
+      }
+
+      this.uploadFile()
+        .then((data) => {
+          const { imageBase64, width, height } = data
+
+          const imageRatio = width / height
+          const rectRatio = rect.width / rect.height
+          let scaledWidth, scaledHeight
+
+          if (imageRatio > rectRatio) {
+            scaledWidth = rect.width
+            scaledHeight = rect.width / imageRatio
+          } else {
+            scaledWidth = rect.height * imageRatio
+            scaledHeight = rect.height
+          }
+          
+          let left, right, bottom, top
+          const centerPoint = {
+            x: rect.left + rect.width / 2,
+            y: rect.top + rect.height / 2
+          }
+
+          left = centerPoint.x - scaledWidth / 2
+          right = centerPoint.x + scaledWidth / 2
+          bottom = centerPoint.y + scaledHeight / 2
+          top = centerPoint.y - scaledHeight / 2
+
+          this.contentContainer.addImageEditor({
+            rect: {
+              left,
+              right,
+              bottom,
+              top
+            },
+            imageBase64
+          })
+        })
+        .catch((error) => {
+          console.error(error)
+        })
+    }
+    // this.reset()
+    this.initStartPoint = null
+    this.initEndPoint = null
+    if (this.shapeEle) this.shapeEle.remove()
+    this.shapeEle = null
+    this.path = []
+    document.removeEventListener('mousemove', this.onMousemove)
+    document.removeEventListener('mouseup', this.onMouseup)
+    
+    document.removeEventListener('touchmove', this.onMousemove)
+    document.removeEventListener('touchend', this.onMouseup)
+  }
+
+  handleMouseMove (event) {
+    let { x, y } = getAbsoluteCoordinate(this.container, event)
+    const { width, height } = this.viewport
+    const scale = this.viewport.scale
+    x = Math.min(width, x)
+    x = Math.max(0, x)
+    y = Math.min(height, y)
+    y = Math.max(0, y)
+    this.initEndPoint = { x, y }
+    if (this.initStartPoint.x !== this.initEndPoint.x && this.initStartPoint.y !== this.initEndPoint.y) {
+      if (!this.shapeEle) {
+        const shapeEle = createElement('div', {
+          'backgroundColor': '#DDE9FF',
+          'position': "absolute",
+          'z-index': 3
+        },
+        {
+          class: 'add-text'
+        })
+        this.shapeEle = shapeEle
+        this.container.append(this.shapeEle)
+      } else {
+        if (event.shiftKey) {
+          const { initStartPoint, initEndPoint } = this
+          const distanceX = Math.abs(initEndPoint.x - initStartPoint.x)
+          const distanceY = Math.abs(initEndPoint.y - initStartPoint.y)
+          const distance = Math.min(distanceX, distanceY)
+          if (initStartPoint.x < initEndPoint.x) {
+            initEndPoint.x = initStartPoint.x + distance
+          } else {
+            initEndPoint.x = initStartPoint.x - distance
+          }
+          if (initStartPoint.y < initEndPoint.y) {
+            initEndPoint.y = initStartPoint.y + distance
+          } else {
+            initEndPoint.y = initStartPoint.y - distance
+          }
+        }
+        const rect = this.calculate(this.initStartPoint, this.initEndPoint)
+
+        const shapeEle = this.shapeEle
+        shapeEle.style.top = `${rect.top}px`
+        shapeEle.style.left = `${rect.left}px`
+        shapeEle.style.width = `${Math.abs(rect.width - 1)}px`
+        shapeEle.style.height = `${Math.abs(rect.height - 1)}px`
+      }
+    }
+  }
+  
+  rectCalc ({ start, end }) {
+    return {
+      top: start.y < end.y ? start.y : end.y,
+      left: start.x < end.x ? start.x : end.x,
+      width: Math.abs(end.x - start.x),
+      height: Math.abs(end.y - start.y),
+    }
+  }
+
+  calculate (start, end) {
+    const initRect = this.rectCalc({ start, end })
+    const actualbdwidth = 1 * this.viewport.scale
+
+    return {
+      top: initRect.top - actualbdwidth,
+      left: initRect.left - actualbdwidth,
+      width: initRect.width + actualbdwidth * 2,
+      height: initRect.height + actualbdwidth * 2,
+    }
+  }
+
+  // 上传图片并获取宽高
+  uploadFile () {
+    return new Promise((resolve, reject) => {
+      const fileInput = document.createElement('input')
+      fileInput.type = 'file'
+      fileInput.accept = '.jpg, .jpeg, .png, .bmp'
+
+      fileInput.onchange = () => {
+        const file = fileInput.files[0]
+
+        if (file.size > 2 * 1024 * 1024) {
+          reject('文件大小超过2M限制')
+          fileInput.remove()
+          window.$message.error('The file size exceeds the 2M limit.', {
+            duration: 3000,
+            // icon: () => h('img', { src: MessageError })
+          })
+          return
+        }
+
+        if (file) {
+          const reader = new FileReader()
+          reader.onload = function (e) {
+            const image = new Image()
+            image.onload = function () {
+              resolve({
+                // file,
+                imageBase64: e.target.result,
+                width: image.width,
+                height: image.height
+              })
+            }
+            image.onerror = function (error) {
+              reject(error)
+            }
+            image.src = e.target.result
+          }
+          reader.onerror = function (error) {
+            reject(error)
+          }
+          reader.readAsDataURL(file)
+          fileInput.remove()
+        }
+      }
+      fileInput.click()
+    })
+  }
+}

+ 120 - 25
packages/core/src/editor/content_container.js

@@ -1,5 +1,7 @@
 import { TextEditor } from './text_editor';
+import { ImageEditor } from './image_editor';
 import AddText from './add_text.js';
+import AddImage from './add_image.js';
 
 export class ContentContainer {
   constructor(options) {
@@ -52,41 +54,62 @@ export class ContentContainer {
 
     await this.messageHandler.sendWithPromise('BeginEdit', {
       editPagePtr: this.editPagePtr,
-      type: 1
+      type: 1 | 2
     })
 
-    let editAreaCount = await this.messageHandler.sendWithPromise('GetEditAreaCount', this.editPagePtr)
+    const editAreaCount = await this.messageHandler.sendWithPromise('GetEditAreaCount', this.editPagePtr)
     
     for (let i = 0; i < editAreaCount; i++) {
-      let editAreaPtr = await this.messageHandler.sendWithPromise('GetEditArea', {
+      const editAreaPtr = await this.messageHandler.sendWithPromise('GetEditArea', {
         editPagePtr: this.editPagePtr,
         index: i
       })
+      if (!editAreaPtr) continue
 
-      if (editAreaPtr) {
-        let isTextPtr = await this.messageHandler.sendWithPromise('IsTextArea', editAreaPtr)
-        if (isTextPtr) {
-          let frameEditor = new TextEditor({
-            eventBus: this.eventBus,
-            contentContainer: this,
-            container: this.contentContainer,
-            pagePtr: this.pagePtr,
-            editPagePtr: this.editPagePtr,
-            editAreaPtr,
-            editAreaIndex: i,
-            viewport: this.viewport,
-            scale: this.scale,
-            messageHandler: this.messageHandler,
-            pageViewer: this.pageViewer
-          })
-          this.frameEditorList.push(frameEditor)
-        }
+      const isTextPtr = await this.messageHandler.sendWithPromise('IsTextArea', editAreaPtr)
+      if (isTextPtr) {
+        const frameEditor = new TextEditor({
+          eventBus: this.eventBus,
+          contentContainer: this,
+          container: this.contentContainer,
+          pagePtr: this.pagePtr,
+          editPagePtr: this.editPagePtr,
+          editAreaPtr,
+          editAreaIndex: i,
+          viewport: this.viewport,
+          scale: this.scale,
+          messageHandler: this.messageHandler,
+          pageViewer: this.pageViewer
+        })
+        this.frameEditorList.push(frameEditor)
+        continue
+      }
+
+      const isImagePtr = await this.messageHandler.sendWithPromise('IsImageArea', editAreaPtr)
+      if (isImagePtr) {
+        const frameEditor = new ImageEditor({
+          eventBus: this.eventBus,
+          contentContainer: this,
+          container: this.contentContainer,
+          pagePtr: this.pagePtr,
+          editPagePtr: this.editPagePtr,
+          editAreaPtr,
+          editAreaIndex: i,
+          viewport: this.viewport,
+          scale: this.scale,
+          messageHandler: this.messageHandler,
+          pageViewer: this.pageViewer
+        })
+        this.frameEditorList.push(frameEditor)
       }
     }
 
     if (this.tool === 'addText') {
       this.addTextManager()
     }
+    if (this.tool === 'addImage') {
+      this.addImageManager()
+    }
 
     this.destroyed = false
     this.rendered = true
@@ -103,6 +126,10 @@ export class ContentContainer {
       this.textManager.tool = ''
       this.textManager.reset()
     }
+    if (this.imageManager) {
+      this.imageManager.tool = ''
+      this.imageManager.reset()
+    }
   }
   
   cancel () {
@@ -120,10 +147,16 @@ export class ContentContainer {
       this.textManager.tool = ''
       this.textManager.reset()
     }
+    if (this.imageManager) {
+      this.imageManager.tool = ''
+      this.imageManager.reset()
+    }
 
     this.destroyed = true
 
     this.eventBus._off('toolChanged', this.onHandleTool)
+
+    this.messageHandler.sendWithPromise('ClearEditPage', this.editPagePtr)
   }
 
   handleTool({
@@ -137,10 +170,25 @@ export class ContentContainer {
       this.textManager.tool = tool
       this.textManager.color = color
     }
+    if (this.imageManager) {
+      this.imageManager.tool = tool
+      this.imageManager.color = color
+    }
 
     if (tool === 'addText') {
+      if (this.imageManager) {
+        this.imageManager.tool = ''
+        this.imageManager.reset()
+      }
       this.addTextManager()
     }
+    if (tool === 'addImage') {
+      if (this.textManager) {
+        this.textManager.tool = ''
+        this.textManager.reset()
+      }
+      this.addImageManager()
+    }
   }
 
   addTextManager () {
@@ -160,19 +208,66 @@ export class ContentContainer {
     }
   }
 
+  addImageManager () {
+    if (this.imageManager) {
+      this.imageManager.init()
+    } else {
+      this.imageManager = new AddImage({
+        tool: this.tool,
+        color: this.color,
+        container: this.pageDiv,
+        viewport: this.viewport,
+        scale: this.scale,
+        page: this.page,
+        eventBus: this.eventBus,
+        contentContainer: this
+      })
+    }
+  }
+
   // 添加编辑区域
   async addTextEditor (data) {
     const { rect, fontData, alignType } = data
-    const param = {
+    const editAreaPtr = await this.messageHandler.sendWithPromise('CreateNewTextArea', {
       pagePtr: this.pagePtr,
       editPagePtr: this.editPagePtr,
       rect,
       fontData,
       alignType
-    }
-    const editAreaPtr = await this.messageHandler.sendWithPromise('CreateNewTextArea', param)
+    })
+
+    if (!editAreaPtr) return
+
+    const frameEditor = new TextEditor({
+      eventBus: this.eventBus,
+      contentContainer: this,
+      container: this.contentContainer,
+      pagePtr: this.pagePtr,
+      editPagePtr: this.editPagePtr,
+      editAreaPtr,
+      editAreaIndex: this.frameEditorList.length,
+      viewport: this.viewport,
+      scale: this.scale,
+      messageHandler: this.messageHandler,
+      newAdd: true,
+      pageViewer: this.pageViewer
+    })
+    this.frameEditorList.push(frameEditor)
+  }
+
+  // 添加图片区域
+  async addImageEditor (data) {
+    const { rect, imageBase64 } = data
+    const editAreaPtr = await this.messageHandler.sendWithPromise('CreateNewImageAreaByStream', {
+      pagePtr: this.pagePtr,
+      editPagePtr: this.editPagePtr,
+      rect,
+      imageBase64
+    })
+
+    if (!editAreaPtr) return
 
-    let frameEditor = new TextEditor({
+    const frameEditor = new ImageEditor({
       eventBus: this.eventBus,
       contentContainer: this,
       container: this.contentContainer,

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1415 - 0
packages/core/src/editor/image_editor.js


+ 4 - 4
packages/core/src/editor/text_editor.js

@@ -493,7 +493,7 @@ export class TextEditor {
         if (isMobileDevice) {
           this.eventBus.dispatch('changeIsEditorPanelDisabled')
         } else {
-          this.eventBus.dispatch('textPropertyChange', {isOpen: true})
+          this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: true })
         }
       }
     }
@@ -765,7 +765,7 @@ export class TextEditor {
 
     this.frameContainer.classList.remove('editing')
 
-    this.eventBus.dispatch('textPropertyChange', {isOpen: false})
+    this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: false })
 
     this.clearSelectText()
     this.cursor?.remove()
@@ -1088,7 +1088,7 @@ export class TextEditor {
       char: this.activeCharPlace
     })
     
-    let style = {
+    const style = {
       color: `rgb(${textStyle.R}, ${textStyle.G}, ${textStyle.B})`,
       opacity: textStyle.Transparency * 100,
       fontStyle: textStyle.IsBold && textStyle.IsItalic ? 3 : !textStyle.IsBold && !textStyle.IsItalic ? 0 : textStyle.IsBold ? 1 : 2,
@@ -1103,7 +1103,7 @@ export class TextEditor {
 
     this.textStyle = style
 
-    this.eventBus.dispatch('textPropertyChange', style)
+    this.eventBus.dispatch('contentPropertyChange', { type: 'text', ...style })
   }
 
   // 某个操作之后,获取光标所在字符的位置

+ 6 - 2
packages/core/src/index.js

@@ -2572,8 +2572,12 @@ class ComPDFKitViewer {
     this.eventBus.dispatch("propertyPanelChanged", props);
   }
 
-  setTextProperty(props) {
-    this.eventBus.dispatch("textPropertyChanged", props);
+  setContentEditorProperty(type, props) {
+    if (type === 'text') {
+      this.eventBus.dispatch("textPropertyChanged", props);
+    } else if (type === 'image') {
+      this.eventBus.dispatch("imagePropertyChanged", props);
+    }
   }
 
   setInitProperty() {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 52 - 6
packages/core/src/worker/compdfkit_worker.js


BIN
packages/webview/public/example/Number Pages.pdf


BIN
packages/webview/public/example/PDF Tech Admin Console Guidelines.pdf


+ 1 - 1
packages/webview/src/components/App/index.vue

@@ -10,7 +10,7 @@
       <RightPanelPageMode />
       <StampPanel />
       <LinkPanel />
-      <EditTextPanel />
+      <ContentEditorPanel />
     </div>
     <div class="mask" :class="{ active: downloading || downloadError !== '' }">
       <div v-show="downloading" class="loading"><Loading /></div>

+ 181 - 11
packages/webview/src/components/EditTextPanel/EditTextPanel.vue

@@ -1,7 +1,9 @@
 <template>
   <div class="editor-panel" id="editorPanel" :class="{ closed: !isOpen }">
-    <div class="title">{{ $t('editorPanel.textProp') }}</div>
-    <div class="edit-container">
+    <div class="title">{{ type === 'text' ? $t('editorPanel.textProp') : $t('editorPanel.imageProp') }}</div>
+
+    <!-- 文本编辑 -->
+    <div v-if="type === 'text'" class="edit-container">
       <div class="color">
         <div class="color-title">{{ $t('editorPanel.fontColor') }}</div>
         <div class="colors-container">
@@ -64,6 +66,68 @@
         </div>
       </div>
     </div>
+
+    <!-- 图片编辑 -->
+    <div v-else class="edit-container">
+      <div class="overview"></div>
+      <div class="rotate">
+        <div class="content-title">{{ $t('editorPanel.rotate') }}</div>
+        <div class="buttons">
+          <div @click="setProperty('rotate', 'left')" :title="$t('editorPanel.rotateLeft')">
+            <RotateImageLeft />{{ $t('editorPanel.rotateLeft') }}
+          </div>
+          <div @click="setProperty('rotate', 'right')" :title="$t('editorPanel.rotateRight')">
+            <RotateImageRight />{{ $t('editorPanel.rotateRight') }}
+          </div>
+        </div>
+      </div>
+      <div class="flips">
+        <div class="content-title">{{ $t('editorPanel.flip') }}</div>
+        <div class="buttons">
+          <div @click="setProperty(0)" :title="$t('editorPanel.flipHorizontal')">
+            <span>
+              <FlipX />
+            </span>
+          </div>
+          <div @click="setProperty(1)" :title="$t('editorPanel.flipVertical')">
+            <span>
+              <FlipY />
+            </span>
+          </div>
+        </div>
+      </div>
+      <div class="opacity">
+        <div class="opacity-title">{{ $t('opacity') }}</div>
+        <div class="tool">
+          <n-slider v-model:value="property.opacity" :tooltip="false" :step="1" :max="100" :min="0" class="width-slider">
+            <template #thumb>
+              <Slider />
+            </template>
+          </n-slider>
+          <n-select v-model:value="opacityLabel" :options="opacityOptions" @update:value="handleBlur" to="#editorPanel"/>
+        </div>
+      </div>
+      <div class="tools">
+        <div class="content-title">{{ $t('editorPanel.tools') }}</div>
+        <div class="buttons">
+          <div @click="setProperty(1)" :title="$t('editorPanel.replace')">
+            <span>
+              <ReplaceImage />
+            </span>
+          </div>
+          <div @click="setProperty(2)" :title="$t('editorPanel.export')">
+            <span>
+              <DownloadImage />
+            </span>
+          </div>
+          <div @click="setProperty(3)" :title="$t('editorPanel.crop')">
+            <span>
+              <TailorImage />
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -80,6 +144,7 @@
   const instance = getCurrentInstance().appContext.app.config.globalProperties
   const $t = instance.$t
 
+  const type = ref('')
   let property = reactive({
     color: '#000000',
     opacity: 100,
@@ -134,30 +199,30 @@
     }
   })
 
-  const isOpen = computed(() => useViewer.isElementOpen('editTextPanel'))
+  const isOpen = computed(() => useViewer.isElementOpen('contentEditorPanel'))
 
   watch(() => property.fontFamily, (newValue, oldValue) => {
-    core.setTextProperty({ fontFamily: newValue })
+    core.setContentEditorProperty(type.value, { fontFamily: newValue })
   })
 
   watch(() => property.fontStyle, (newValue, oldValue) => {
-    core.setTextProperty({ fontStyle: newValue })
+    core.setContentEditorProperty(type.value, { fontStyle: newValue })
   })
 
   watch(() => property.opacity, (newValue, oldValue) => {
     opacityLabel.value = newValue + '%'
-    core.setTextProperty({ opacity: property.opacity })
+    core.setContentEditorProperty(type.value, { opacity: property.opacity })
   })
 
   watch(() => property.fontSize, (newValue, oldValue) => {
     if (typeof newValue === 'number') {
-      core.setTextProperty({ fontSize: property.fontSize })
+      core.setContentEditorProperty(type.value, { fontSize: property.fontSize })
     }
   })
 
   const setActiveToolColor = (color) => {
     property.color = color
-    core.setTextProperty({ color })
+    core.setContentEditorProperty(type.value, { color })
   }
 
   const handleBlur = () => {
@@ -167,13 +232,16 @@
   const changeAlign = (value) => {
     property.alignType = value
 
-    core.setTextProperty({ alignType: property.alignType })
+    core.setContentEditorProperty(type.value, { alignType: property.alignType })
   }
 
   const updateProperty = (props) => {
     for (const item in props) {
+      if (item === 'type') {
+        type.value = props[item]
+      }
       if (item === 'isOpen') {
-        props.isOpen ? useViewer.openElement('editTextPanel') : useViewer.closeElement('editTextPanel')
+        props.isOpen ? useViewer.openElement('contentEditorPanel') : useViewer.closeElement('contentEditorPanel')
       }
 
       for (const key in property) {
@@ -187,7 +255,13 @@
       }
     }
   }
-  core.addEvent('textPropertyChange', updateProperty)
+  core.addEvent('contentPropertyChange', updateProperty)
+
+  const setProperty = (key, value) => {
+    const data = {}
+    data[key] = value
+    core.setContentEditorProperty(type.value, data)
+  }
 </script>
 
 <style lang="scss" scoped>
@@ -264,6 +338,7 @@
           }
         }
       }
+      
       .opacity {
         .opacity-title {
           margin-top: 12px;
@@ -302,6 +377,7 @@
           }
         }
       }
+
       .text {
         margin-top: 12px;
         .text-title {
@@ -401,6 +477,100 @@
           color: var(--c-text);
         }
       }
+
+      .overview {
+        width: 100%;
+        height: 80px;
+        border: 1px solid #E2E3E6;
+        background: var(--c-right-side-content-fillbox-bg);
+      }
+
+      .tools,
+      .flips {
+        margin-top: 12px;
+        .content-title {
+          font-size: 14px;
+          font-weight: 700;
+          line-height: 32px;
+          color: var(--c-side-title);
+        }
+
+        .buttons {
+          margin-top: 8px;
+          display: inline-flex;
+          border: 1px solid #0000001F;
+          background: var(--c-right-side-content-fillbox-bg);
+
+          :hover {
+            background: #DDE9FF;
+            svg {
+              color: #43474D;
+            }
+          }
+
+          div {
+            padding: 8px 0px;
+            width: 48px;
+            height: 32px;
+            text-align: center;
+            cursor: pointer;
+            span {
+              display: inline-block;
+              font-size: 0;
+              width: 100%;
+              svg {
+                color: var(--c-text);
+              }
+            }
+            & + div span {
+              border-left: var(--c-edit-right-page) 1px solid;
+            }
+          }
+        }
+      }
+
+      .rotate {
+        margin-top: 12px;
+
+        .content-title {
+          font-size: 14px;
+          font-weight: 700;
+          line-height: 32px;
+          color: var(--c-side-title);
+        }
+
+        .buttons {
+          margin-top: 8px;
+          display: inline-flex;
+          width: 100%;
+          gap: 5px;
+
+          div {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex: 1;
+            padding: 8px 0px;
+            height: 32px;
+            text-align: center;
+            cursor: pointer;
+            border: 1px solid #0000001F;
+            border-radius: 1px;
+            background: var(--c-right-side-content-fillbox-bg);
+
+            &:hover {
+              background: #DDE9FF;
+              svg {
+                color: #43474D;
+              }
+            }
+
+            svg {
+              margin-right: 8px;
+            }
+          }
+        }
+      }
     }
   }
   @media screen and (max-width: 640px) {

+ 4 - 4
packages/webview/src/components/EditToolbar/EditToolBar.vue

@@ -3,10 +3,10 @@
     <Button class="operate" :class="{ active: activeTool === 'addText' }" @click="changeActiveTool('addText')" title="Add Text">
       <AddText />{{ $t('header.addText') }}
     </Button>
-    <!-- <Button class="operate" :class="{ active: activeTool === 'addImage' }" @click="changeActiveTool('addImage')" title="Add Image">
+    <Button class="operate" :class="{ active: activeTool === 'addImage' }" @click="changeActiveTool('addImage')" title="Add Image">
       <AddImage />{{ $t('header.addImage') }}
     </Button>
-    <div class="divider pc"></div>
+    <!-- <div class="divider pc"></div>
     <Button class="history" :title="$t('header.undo')">
       <EditBack />
     </Button>
@@ -37,9 +37,9 @@
     switchAnnotationEditorMode(0)
     
     // if (tool === 'addText') {
-    //   useViewer.toggleElement('editTextPanel')
+    //   useViewer.toggleElement('contentEditorPanel')
     // } else {
-    //   useViewer.closeElement('editTextPanel')
+    //   useViewer.closeElement('contentEditorPanel')
     // }
   }
 </script>

+ 12 - 10
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -192,7 +192,7 @@ onMounted(async () => {
     toggleButton: document.querySelector('.toggle-button')
   })
   let initialDoc = getHashParameters('d', '')
-  initialDoc = initialDoc ? JSON.parse(initialDoc) : ''
+  initialDoc = initialDoc ? JSON.parse(initialDoc) : './example/PDF Tech Admin Console Guidelines.pdf'
   initialDoc = Array.isArray(initialDoc) ? initialDoc : [initialDoc]
   const activeTab = useViewer.activeTab || 0
   initialDoc = initialDoc[activeTab]
@@ -669,12 +669,13 @@ onMounted(async () => {
   }
 
   .frame-container {
-    .text-container {
+    .text-container,
+    .image-container {
       border-style: dashed;
+    }
 
-      textarea {
-        cursor: default;
-      }
+    .text-container textarea {
+      cursor: default;
     }
 
     &.selected {
@@ -682,13 +683,14 @@ onMounted(async () => {
     }
 
     &.editing {
-      .text-container {
+      .text-container,
+      .image-container {
         border-style: solid;
         z-index: 1;
-
-        textarea {
-          cursor: text;
-        }
+      }
+      
+      .text-container textarea {
+        cursor: text;
       }
     }
   }

+ 5 - 0
packages/webview/src/components/Icon/DownloadImage.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M11.8537 7.17423L8.5001 10.5279L8.5001 1.60001V1.10001H7.5001V1.60001L7.5001 10.5277L4.14666 7.17423L3.7931 6.82068L3.086 7.52778L3.43955 7.88134L7.64664 12.0884C7.8419 12.2837 8.15849 12.2837 8.35375 12.0884L12.5608 7.88134L12.9144 7.52778L12.2073 6.82068L11.8537 7.17423ZM1.6001 13.9H1.1001V14.9H1.6001H14.4001H14.9001V13.9H14.4001H1.6001Z" fill="currentColor"/>
+  </svg>
+</template>

+ 5 - 0
packages/webview/src/components/Icon/FlipX.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M7.52941 14.5H0L7.52941 1.5V14.5ZM9.47059 13.5H14.2652L9.47059 5.2218V13.5ZM16 14.5H14.8444H9.47059H8.47059V13.5V3.49524V1.5L9.47059 3.22656L15.4208 13.5L16 14.5Z" fill="currentColor"/>
+  </svg>
+</template>

+ 5 - 0
packages/webview/src/components/Icon/FlipY.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 7.5H13.1353H1.5H0.5V6.5V1.60353V0.5L1.5 0.966667L13.3571 6.5L15.5 7.5ZM10.9924 6.5H1.5V2.0702L10.9924 6.5ZM15.5 8.5H0.5V15.5L15.5 8.5Z" fill="currentColor"/>
+  </svg>
+</template>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
packages/webview/src/components/Icon/ReplaceImage.vue


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
packages/webview/src/components/Icon/RotateImageLeft.vue


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
packages/webview/src/components/Icon/RotateImageRight.vue


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
packages/webview/src/components/Icon/TailorImage.vue


+ 2 - 2
packages/webview/src/components/ToggleRightPanelButton/index.vue

@@ -16,7 +16,7 @@
   const isActive = computed(() => {
     return useViewer.isElementOpen(item.element)
   })
-  const editTextPanelIsActive = computed(() => useViewer.isElementOpen('editTextPanel'))
+  const editTextPanelIsActive = computed(() => useViewer.isElementOpen('contentEditorPanel'))
 
   let isDisabled = ref(true)
   let isEditorPanelDisabled = ref(true)
@@ -41,7 +41,7 @@
         if (!isEditing && !editTextPanelIsActive.value) {
           isEditorPanelDisabled.value = true
         } else {
-          useViewer.toggleElement('editTextPanel')
+          useViewer.toggleElement('contentEditorPanel')
           if (!isEditing) {
             isEditorPanelDisabled.value = true
           }

+ 2 - 2
packages/webview/src/components/Toolbar/Toolbar.vue

@@ -18,8 +18,8 @@
 
       <!-- Compare -->
       <ComparedToolbar v-else-if="item.type === 'comparedToolbar'" />
-      <!-- Editor -->
-      <EditToolBar v-else-if="item.type === 'editTextToolbar'" />
+      <!-- Content Editor -->
+      <ContentEditorToolBar v-else-if="item.type === 'contentEditorToolbar'" />
     </template>
   </div>
 </template>

+ 2 - 2
packages/webview/src/core/index.js

@@ -44,7 +44,7 @@ import removePassword from './removePassword'
 import checkPassword from './checkPassword'
 import handleCreateStatus from './handleCreateStatus'
 import triggerPrinting from './triggerPrinting'
-import setTextProperty from './setTextProperty'
+import setContentEditorProperty from './setContentEditorProperty'
 import getOutlines from './getOutlines'
 import search from './search'
 import setActiveSearchResult from './setActiveSearchResult'
@@ -108,7 +108,7 @@ export default {
   checkPassword,
   handleCreateStatus,
   triggerPrinting,
-  setTextProperty,
+  setContentEditorProperty,
   getOutlines,
   search,
   setActiveSearchResult,

+ 3 - 0
packages/webview/src/core/setContentEditorProperty.js

@@ -0,0 +1,3 @@
+import core from '@/core'
+
+export default (type, props) => core.getDocumentViewer().setContentEditorProperty(type, props)

+ 0 - 3
packages/webview/src/core/setTextProperty.js

@@ -1,3 +0,0 @@
-import core from '@/core'
-
-export default (props) => core.getDocumentViewer().setTextProperty(props)

+ 4 - 4
packages/webview/src/stores/modules/viewer.js

@@ -33,7 +33,7 @@ export const useViewerStore = defineStore({
       signCreatePanel: false,
       downloadSettingDialog: false,
       printSettingDialog: false,
-      editTextPanel: false,
+      contentEditorPanel: false,
       contentEditorPreventDialog: false,
       docEditorPreventDialog: false,
       insertPageSettingDialog: false,
@@ -237,7 +237,7 @@ export const useViewerStore = defineStore({
       ],
       editor: [
         {
-          type: 'editTextToolbar'
+          type: 'contentEditorToolbar'
         }
       ]
     },
@@ -360,7 +360,7 @@ export const useViewerStore = defineStore({
         signCreatePanel: false,
         downloadSettingDialog: false,
         printSettingDialog: false,
-        editTextPanel: false,
+        contentEditorPanel: false,
         insertPageSettingDialog: false,
         deletePageDialog: false,
         extractPageSettingDialog: false,
@@ -457,7 +457,7 @@ export const useViewerStore = defineStore({
       this.toolMode = mode
       this.activeElements['stampPanel'] = false
       this.activeElements['linkPanel'] = false
-      this.activeElements['editTextPanel'] = false
+      this.activeElements['contentEditorPanel'] = false
       core.setToolMode(mode)
     },
     setDownloading (boolean) {