Skip to content
ComPDF
Guides

创建、移动、删除文本、图像和路径

ComPDF 提供了完整丰富的创建、移动、删除文本,图像和路径的方法。

通过 CPDFReaderView操作

CPDFReaderView默认提供基本的交互能力,允许用户创建和删除文本,图像和路径,拖拽移动图片,文字块和路径位置,调整图片,文字块和路径大小等,实现类似在常见文字处理软件中的操作。

设置上下文菜单

如果需要复制、粘贴、剪切或删除文本,图片或路径,可以通过 CPDFReaderViewsetContextMenuShowListener事件在上下文菜单中添加这些操作方法。

以下是如何在上下文菜单中添加复制、粘贴、删除等操作的示例代码:

edit_text_area_menu_layout.xml:

xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/edit_copy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:gravity="center"
        android:text="copy" />
    <TextView
        android:id="@+id/edit_paste"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:gravity="center"
        android:text="paste" />
    <TextView
        android:id="@+id/edit_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:gravity="center"
        android:text="delete" />

</LinearLayout>
java
public class DemoContextMenuHelper extends CPDFContextMenuShowHelper {

  public DemoContextMenuHelper(CPDFReaderView cpdfReaderView) {
    super(cpdfReaderView);
  }

  @Override
  public View getEditTextAreaContentView(CPDFPageView cpdfPageView, LayoutInflater layoutInflater, CPDFEditSelections cpdfEditSelections) {
    View contentView = layoutInflater.inflate(R.layout.edit_text_area_menu_layout, null);
    invokeOnClickListener(contentView, v -> {
      try {
        int id = v.getId();
        if (id == R.id.edit_copy) {
          pageView.operateEditTextArea(CPDFPageView.EditTextAreaFuncType.COPY);
        } else if (id == R.id.edit_paste) {
          pageView.operateEditText(CPDFPageView.EditTextFuncType.PASTE);
        } else if (id == R.id.edit_delete) {
          pageView.operateEditTextArea(CPDFPageView.EditTextAreaFuncType.DELETE);
        }
      } finally {
        dismissContextMenu();
      }
    }, R.id.edit_copy, R.id.edit_paste, R.id.edit_delete);
    return contentView;
  }
}
...
// 初始化CPDFReaderView
CPDFReaderView readerView = findViewById(R.id.readerView);
CPDFDocument document = new CPDFDocument(context);
document.open(pdfPath);
readerView.setPDFDocument(document);
// 设置自定义的上下文菜单
readerView.setContextMenuShowListener(new DemoContextMenuHelper(readerView));
kotlin
class DemoContextMenuHelper(cpdfReaderView: CPDFReaderView) :
CPDFContextMenuShowHelper(cpdfReaderView) {

  override fun getEditTextAreaContentView(
    cpdfPageView: CPDFPageView,
    layoutInflater: LayoutInflater,
    cpdfEditSelections: CPDFEditSelections
  ): View {
  
    val contentView: View = layoutInflater.inflate(R.layout.edit_text_area_menu_layout, null)
    invokeOnClickListener(contentView, { v: View ->
        try {
            val id = v.id
            if (id == R.id.edit_copy) {
                pageView.operateEditTextArea(CPDFPageView.EditTextAreaFuncType.COPY)
            } else if (id == R.id.edit_paste) {
                pageView.operateEditText(CPDFPageView.EditTextFuncType.PASTE)
            } else if (id == R.id.edit_delete) {
                pageView.operateEditTextArea(CPDFPageView.EditTextAreaFuncType.DELETE)
            }
        } finally {
            dismissContextMenu()
        }
    }, R.id.edit_copy, R.id.edit_paste, R.id.edit_delete)
    return contentView
  }
}
...
// 初始化CPDFReaderView
val readerView = findViewById<CPDFReaderView>(R.id.readerView)
val document = CPDFDocument(context)
document.open(pdfPath)
readerView.pdfDocument = document
// 设置自定义的上下文菜单
readerView.contextMenuShowListener = DemoContextMenuHelper(readerView)

插入文字和图片

交互创建

您可以通过 CPDFEditManagerbeginEdit方法来指定是否可以插入文字和图片块。下面的代码将向您展示如何执行此操作:

java
CPDFEditManager editManager = cpdfReaderView.getEditManager();
// 允许插入图片。
editManager.beginEdit(CPDFEditPage.LoadImage);
// 允许插入文字。
editManager.beginEdit(CPDFEditPage.LoadText);
// 允许取消插入内容状态
editManager.beginEdit(CPDFEditPage.LoadNone);

同时您也可以通过点击PDF页面区域来插入图片

java
private CImageResultLauncher imageResultLauncher = new CImageResultLauncher(this);

// 当点击PDF页面区域时会触发该回调
cpdfReaderView.setSelectImageCallback(() -> {
  // 自行实现选择图片功能
  imageResultLauncher.launch(RequestType.PHOTO_ALBUM,
                             result -> cpdfReaderView.addEditImage(result));
});

编程创建

您也可以通过编程的方式插入文本或图片

  • 插入文本
java
CPDFPage page = document.pageAtIndex(pageIndex);

Point point = new Point((int) pointX, (int) pointY);
RectF area = new RectF(point.x, point.y, point.x, point.y);

// Get the edit page object
CPDFEditPage cpdfEditPage = page.getEditPage(false);
cpdfEditPage.beginEdit(CPDFEditPage.LoadTextImage);

if (cpdfEditPage == null || !cpdfEditPage.isValid()) {
  return null;
}

PDFEditAlignType alignType = CPDFEnumConvertUtil.stringToEditAlignType(alignment);
// Create a new text area
CPDFEditTextArea editTextArea = cpdfEditPage.createNewTextArea(area, psName,
       (float) fontSize, Color.parseColor(fontColor), (int) alpha, false, false, alignType);

if (editTextArea != null && editTextArea.isValid()) {
  // Get the start and end positions for text insertion
  CPDFEditCharItem begin = editTextArea.getBeginCharPlace();
  CPDFEditCharItem end = editTextArea.getEndCharPlace();
  // Insert the content into the text area
  CPDFEditCharItem charItem = editTextArea.insertTextRange(begin.getPlace(),
                                                           end.getPlace(), content);

  RectF currentRect = editTextArea.getFrame(true);
  if (maxWidth != 0) {
    currentRect.right = area.left + (float) maxWidth;
    editTextArea.setFrame(currentRect, true);
  }
  // 如果使用了CPDFReaderView展示当前文档,没有进入setViewMode(ViewMode.PDFEdit)模式,请调用结束编辑方法。
  if (!isEditMode) {
    page.endEdit();
  }
  return editTextArea;
}
kotlin
val page: CPDFPage = document.pageAtIndex(pageIndex)

val point = Point(pointX.toInt(), pointY.toInt())
val area = RectF(point.x.toFloat(), point.y.toFloat(), point.x.toFloat(), point.y.toFloat())

// 获取编辑页对象
val editPage: CPDFEditPage? = page.getEditPage(false)

// 开始编辑(加载文本和图片资源)
editPage?.beginEdit(CPDFEditPage.LoadTextImage)

if (editPage == null || !editPage.isValid) {
    return null
}

val alignType: PDFEditAlignType =
    CPDFEnumConvertUtil.stringToEditAlignType(alignment)

// 创建新的文本区域
val editTextArea: CPDFEditTextArea? = editPage.createNewTextArea(
    area,
    psName,
    fontSize.toFloat(),
    Color.parseColor(fontColor),
    alpha,
    false,
    false,
    alignType
)

if (editTextArea != null && editTextArea.isValid) {

    // 获取文本插入的起始和结束位置
    val begin: CPDFEditCharItem = editTextArea.beginCharPlace
    val end: CPDFEditCharItem = editTextArea.endCharPlace

    // 在文本区域中插入内容
    editTextArea.insertTextRange(
        begin.place,
        end.place,
        content
    )

    val currentRect = editTextArea.getFrame(true)

    if (maxWidth != 0) {
        currentRect.right = area.left + maxWidth.toFloat()
        editTextArea.setFrame(currentRect, true)
    }

    // 如果使用 CPDFReaderView 显示当前文档,且未进入 ViewMode.PDFEdit 模式
    // 则需要主动结束编辑
    if (!isEditMode) {
        page.endEdit()
    }

    return editTextArea
}

return null
  • 插入图片
java
int pageIndex = 0;
double pointX = 100;
double pointY = 100;

double width = 200;
CPDFPage page = document.pageAtIndex(pageIndex);

// Get the edit page object
CPDFEditPage cpdfEditPage = page.getEditPage(false);

if (cpdfEditPage == null || !cpdfEditPage.isValid()) {
  return;
}

// Start edit mode
cpdfEditPage.beginEdit(CPDFEditPage.LoadImage);

String imagePath = "xxx.jpg"
  if (imagePath == null) {
    Log.e("CPDFEditAreaUtil", "Failed to get image path");
    callback.callback(null);
    return;
  }

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
int bitmapWidth = options.outWidth;
int bitmapHeight = options.outHeight;

if (bitmapWidth <= 0 || bitmapHeight <= 0) {
  Log.e("CPDFEditAreaUtil", "Invalid bitmap dimensions");
  return;
}

RectF insertRect = calculateInsertRect(
  page,
  pointX,
  pointY,
  bitmapWidth,
  bitmapHeight,
  width);

CPDFEditArea editArea = cpdfEditPage.createNewImageArea(
  insertRect,
  imagePath,
  null);

// 如果使用了CPDFReaderView展示当前文档,没有进入setViewMode(ViewMode.PDFEdit)模式,请调用结束编辑方法。
if (!isEditMode) {
  page.endEdit();
}

// ---------------------------------------------------
// 计算图片插入区域
private static RectF calculateInsertRect(
  CPDFPage page,
  double pointX,
  double pointY,
  int bitmapWidth,
  int bitmapHeight,
  @Nullable Double targetWidth) {

  // 1. 获取页面尺寸(处理旋转)
  RectF pageSize = page.getSize();
  int pageWidth = (int) pageSize.width();
  int pageHeight = (int) pageSize.height();

  int rotation = page.getRotation();
  if (rotation == 90 || rotation == 270) {
    int temp = pageWidth;
    pageWidth = pageHeight;
    pageHeight = temp;
  }

  // 2. 计算图片宽高比
  float aspectRatio = (float) bitmapWidth / bitmapHeight;

  // 3. 计算目标尺寸
  int width = (targetWidth != null && targetWidth > 0)
    ? targetWidth.intValue()
    : bitmapWidth;

  int height = (int) (width / aspectRatio);

  // 4. 限制尺寸不超过页面大小(等比缩放)
  if (width > pageWidth) {
    width = pageWidth;
    height = (int) (width / aspectRatio);
  }

  if (height > pageHeight) {
    height = pageHeight;
    width = (int) (height * aspectRatio);
  }

  // 5. 计算左上角坐标并进行边界修正
  float left = clamp((float) pointX, 0, pageWidth - width);
  float top = clamp((float) pointY, height, pageHeight);

  // 6. 返回插入区域(PDF 坐标系,y 轴向上)
  return new RectF(
    left,
    top,
    left + width,
    top - height
  );
}

/**
 * 将值限制在指定区间内
 */
private static float clamp(float value, float min, float max) {
  return Math.max(min, Math.min(value, max));
}
kotlin
val pageIndex = 0
val pointX = 100.0
val pointY = 100.0
val width = 200.0

val page: CPDFPage = document.pageAtIndex(pageIndex)

// 获取编辑页对象
val editPage: CPDFEditPage? = page.getEditPage(false)
if (editPage == null || !editPage.isValid) return

// 开始编辑模式(加载图片资源)
editPage.beginEdit(CPDFEditPage.LoadImage)

val imagePath = "xxx.jpg"
if (imagePath.isEmpty()) {
    Log.e("CPDFEditAreaUtil", "Failed to get image path")
    callback.callback(null)
    return
}

// 获取图片尺寸
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(imagePath, options)
val bitmapWidth = options.outWidth
val bitmapHeight = options.outHeight

if (bitmapWidth <= 0 || bitmapHeight <= 0) {
    Log.e("CPDFEditAreaUtil", "Invalid bitmap dimensions")
    return
}

// 计算插入区域
val insertRect = calculateInsertRect(
    page,
    pointX,
    pointY,
    bitmapWidth,
    bitmapHeight,
    width
)

// 创建新的图片区域
val editArea: CPDFEditArea? = editPage.createNewImageArea(
    insertRect,
    imagePath,
    null
)

// 如果使用了 CPDFReaderView 展示文档,且未进入 PDFEdit 模式,需要结束编辑
if (!isEditMode) {
    page.endEdit()
}

// ---------------------------------------------------
// 计算图片插入区域
fun calculateInsertRect(
    page: CPDFPage,
    pointX: Double,
    pointY: Double,
    bitmapWidth: Int,
    bitmapHeight: Int,
    targetWidth: Double?
): RectF {

    // 1. 获取页面尺寸(处理旋转)
    var pageWidth = page.getSize().width().toInt()
    var pageHeight = page.getSize().height().toInt()

    val rotation = page.getRotation()
    if (rotation == 90 || rotation == 270) {
        val temp = pageWidth
        pageWidth = pageHeight
        pageHeight = temp
    }

    // 2. 图片宽高比
    val aspectRatio = bitmapWidth.toFloat() / bitmapHeight

    // 3. 计算目标尺寸
    var width = if (targetWidth != null && targetWidth > 0) targetWidth.toInt() else bitmapWidth
    var height = (width / aspectRatio).toInt()

    // 4. 限制尺寸不超过页面大小(等比缩放)
    if (width > pageWidth) {
        width = pageWidth
        height = (width / aspectRatio).toInt()
    }
    if (height > pageHeight) {
        height = pageHeight
        width = (height * aspectRatio).toInt()
    }

    // 5. 计算左上角坐标并边界修正
    val left = clamp(pointX.toFloat(), 0f, (pageWidth - width).toFloat())
    val top = clamp(pointY.toFloat(), height.toFloat(), pageHeight.toFloat())

    // 6. 返回插入区域(PDF 坐标系,y 轴向上)
    return RectF(left, top, left + width, top - height)
}

// 限制值在指定区间
fun clamp(value: Float, min: Float, max: Float): Float {
    return max(min, min(value, max))
}