HarmonyOS 6学习:PDF标注功能实现与问题解决方案
摘要:本文针对HarmonyOS 6中PDF标注功能的fillColor属性失效问题,提出了两种解决方案。方案一通过PdfBorder设置填充颜色,方案二则采用LineAnnotationInfo手动绘制边框。在AI旅行助手应用中,这些方案实现了不同颜色标注景点、餐厅等功能,解决了默认黑色方框的问题。文章详细对比了两种方案的复杂度、性能等特性,并给出最佳实践建议,包括优先使用方案一、颜色格式转换等
引言:从AI旅行助手到PDF标注的实际需求
在我们之前实现的AI旅行助手项目中,用户常常遇到这样的场景:生成的旅行攻略以PDF格式分享给朋友后,需要在特定景点、餐厅或注意事项上做标记。用户反馈说,现有的PDF阅读器标注功能不够直观,特别是方框标注的颜色设置总是不生效,标注出来的方框一直是黑色,无法区分不同类型的内容。
比如,用户想用红色方框标出必去景点,用绿色方框标出美食推荐,用黄色方框标出交通提示。但实际使用时,无论设置什么颜色,渲染出来的方框都是黑色,这让标注失去了分类和强调的意义。
一、问题重现:fillColor设置无效的困扰
1.1 问题现象描述
在HarmonyOS 6的PDF标注功能中,当我们使用SquareAnnotationInfo添加方形标注时,会遇到一个令人困惑的问题:无论将fillColor属性设置为什么颜色值,最终渲染效果都是黑色。
// 问题代码示例
let squareAnnotationInfo: pdf.Pdf.SquareAnnotationInfo = {
rect: { left: 100, top: 100, right: 200, bottom: 200 },
content: "这是一个重要的备注",
fillColor: 0xFFFF0000, // 期望是红色,但实际渲染为黑色
borderWidth: 2,
borderColor: 0xFF0000FF // 边框颜色
};
问题表现:
-
在代码中明确设置了
fillColor: 0xFFFF0000(ARGB格式,红色) -
程序运行没有报错,标注也能正常添加
-
但实际显示时,方框的填充颜色始终是黑色
-
边框颜色设置正常,可以正确显示
1.2 问题影响范围
这个问题不仅影响方形标注,还影响其他类型的填充标注:
-
圆形标注(CircleAnnotationInfo)
-
多边形标注(PolygonAnnotationInfo)
-
高亮标注(HighlightAnnotationInfo)
实际上,所有涉及fillColor属性的标注类型都可能受到影响。
二、解决方案一:使用PdfBorder设置填充颜色
2.1 核心思路
既然直接设置fillColor无效,我们可以通过设置border属性来间接实现填充效果。具体来说,就是通过设置PdfBorder的fillColor属性来实现填充颜色的控制。
2.2 完整实现代码
import { pdf } from '@kit.PdfKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* PDF标注管理器
*/
class PdfAnnotationManager {
private pdfDocument: pdf.Pdf.PdfDocument | null = null;
private currentPageIndex: number = 0;
/**
* 加载PDF文档
*/
async loadPdfDocument(filePath: string): Promise<void> {
try {
// 创建Pdf实例
const pdfController: pdf.Pdf.PdfController = new pdf.Pdf.PdfController();
// 加载PDF文档
this.pdfDocument = await pdfController.loadDocument(filePath);
console.log('PDF文档加载成功');
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`加载PDF失败: code: ${err.code}, message: ${err.message}`);
throw new Error('PDF加载失败');
}
}
/**
* 方案一:使用PdfBorder设置填充颜色
*/
async addSquareAnnotationWithBorder(): Promise<void> {
if (!this.pdfDocument) {
throw new Error('请先加载PDF文档');
}
try {
// 获取当前页
const pdfPage: pdf.Pdf.PdfPage = await this.pdfDocument.getPage(this.currentPageIndex);
// 创建PdfBorder实例
const pdfBorder: pdf.Pdf.PdfBorder = {
fillColor: 0xFFFF0000, // 红色填充
borderColor: 0xFF0000FF, // 蓝色边框
borderWidth: 2, // 边框宽度2px
borderStyle: pdf.Pdf.LineDashStyle.SOLID // 实线边框
};
// 创建方形标注信息
const squareAnnotationInfo: pdf.Pdf.SquareAnnotationInfo = {
rect: {
left: 100, // 距离左边100px
top: 100, // 距离顶部100px
right: 300, // 宽度200px
bottom: 200 // 高度100px
},
content: "重要景点标注", // 标注内容
border: pdfBorder, // 使用border设置填充和边框
opacity: 0.7 // 设置透明度
};
// 添加标注
const annotation: pdf.Pdf.PdfAnnotation = await pdfPage.addAnnotation(squareAnnotationInfo);
console.log('方形标注添加成功,ID:', annotation.getAnnotationId());
// 保存更改
await this.savePdfDocument();
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`添加标注失败: code: ${err.code}, message: ${err.message}`);
throw new Error('标注添加失败');
}
}
/**
* 保存PDF文档
*/
private async savePdfDocument(): Promise<void> {
if (!this.pdfDocument) {
return;
}
try {
const filePath = '/data/storage/el2/base/haps/entry/temp/annotated.pdf';
await this.pdfDocument.writeFile(filePath);
console.log('PDF保存成功:', filePath);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`保存PDF失败: code: ${err.code}, message: ${err.message}`);
}
}
}
2.3 在AI旅行助手中的应用
在AI旅行助手中,我们可以根据不同类型的内容使用不同颜色的标注:
// 旅行攻略标注管理器
class TravelGuideAnnotationManager extends PdfAnnotationManager {
// 标注颜色定义
private annotationColors = {
SCENIC_SPOT: 0xFFFF0000, // 红色:重要景点
RESTAURANT: 0xFF00FF00, // 绿色:餐厅推荐
TRANSPORTATION: 0xFFFFFF00, // 黄色:交通提示
HOTEL: 0xFFFFA500, // 橙色:酒店信息
TIP: 0xFF00BFFF // 蓝色:旅行小贴士
};
/**
* 添加旅行攻略标注
*/
async addTravelGuideAnnotation(
type: string,
rect: pdf.Pdf.Rect,
content: string
): Promise<void> {
const color = this.annotationColors[type] || 0xFF000000;
const pdfBorder: pdf.Pdf.PdfBorder = {
fillColor: color,
borderColor: 0xFF000000, // 黑色边框
borderWidth: 1,
borderStyle: pdf.Pdf.LineDashStyle.SOLID
};
const squareAnnotationInfo: pdf.Pdf.SquareAnnotationInfo = {
rect: rect,
content: content,
border: pdfBorder,
opacity: 0.3 // 半透明填充
};
// 调用父类方法添加标注
await this.addAnnotation(squareAnnotationInfo);
}
/**
* 批量添加攻略标注
*/
async addBatchAnnotations(annotations: Array<{
type: string;
rect: pdf.Pdf.Rect;
content: string;
}>): Promise<void> {
for (const annotation of annotations) {
await this.addTravelGuideAnnotation(
annotation.type,
annotation.rect,
annotation.content
);
}
}
}
三、解决方案二:使用LineAnnotationInfo手动绘制边框
3.1 核心思路
如果第一种方案在某些情况下仍然不生效,我们可以采用更底层的方案:使用LineAnnotationInfo手动绘制方框的四条边。虽然这种方法稍微复杂,但提供了完全的控制权。
3.2 完整实现代码
/**
* 手动绘制方框标注
*/
class ManualSquareAnnotationManager {
private pdfDocument: pdf.Pdf.PdfDocument | null = null;
private currentPageIndex: number = 0;
/**
* 手动绘制方框标注
*/
async drawSquareAnnotationManually(
rect: pdf.Pdf.Rect,
content: string,
fillColor: number,
borderColor: number
): Promise<void> {
if (!this.pdfDocument) {
throw new Error('请先加载PDF文档');
}
try {
const pdfPage: pdf.Pdf.PdfPage = await this.pdfDocument.getPage(this.currentPageIndex);
// 1. 绘制填充矩形(通过设置较粗的边框实现)
await this.drawFilledRectangle(pdfPage, rect, fillColor);
// 2. 绘制四条边形成边框
await this.drawSquareBorder(pdfPage, rect, borderColor);
// 3. 添加文本标注
await this.addTextAnnotation(pdfPage, rect, content);
console.log('手动绘制方框标注完成');
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`绘制标注失败: code: ${err.code}, message: ${err.message}`);
throw new Error('标注绘制失败');
}
}
/**
* 绘制填充矩形
*/
private async drawFilledRectangle(
pdfPage: pdf.Pdf.PdfPage,
rect: pdf.Pdf.Rect,
fillColor: number
): Promise<void> {
// 注意:lineColor使用的是BGR格式,而不是ARGB
// 0xFF0000 是蓝色,不是红色
const bgrColor = this.argbToBgr(fillColor);
// 上边
const topLine: pdf.Pdf.LineAnnotationInfo = {
rect: {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.top + 20 // 填充高度
},
lineColor: bgrColor,
lineWidth: 20,
opacity: 0.3
};
await pdfPage.addAnnotation(topLine);
// 中间填充部分(重复绘制多条线来实现填充效果)
const fillHeight = rect.bottom - rect.top - 40;
const lineCount = Math.ceil(fillHeight / 20);
for (let i = 1; i < lineCount; i++) {
const lineRect = {
left: rect.left,
top: rect.top + 20 * i,
right: rect.right,
bottom: rect.top + 20 * (i + 1)
};
const fillLine: pdf.Pdf.LineAnnotationInfo = {
rect: lineRect,
lineColor: bgrColor,
lineWidth: 20,
opacity: 0.3
};
await pdfPage.addAnnotation(fillLine);
}
// 下边
const bottomLine: pdf.Pdf.LineAnnotationInfo = {
rect: {
left: rect.left,
top: rect.bottom - 20,
right: rect.right,
bottom: rect.bottom
},
lineColor: bgrColor,
lineWidth: 20,
opacity: 0.3
};
await pdfPage.addAnnotation(bottomLine);
}
/**
* 绘制方框边框
*/
private async drawSquareBorder(
pdfPage: pdf.Pdf.PdfPage,
rect: pdf.Pdf.Rect,
borderColor: number
): Promise<void> {
const bgrColor = this.argbToBgr(borderColor);
// 绘制四条边
const borders = [
// 上边框
{
rect: {
left: rect.left,
top: rect.top,
right: rect.right,
bottom: rect.top + 2
},
lineColor: bgrColor,
lineWidth: 2
},
// 下边框
{
rect: {
left: rect.left,
top: rect.bottom - 2,
right: rect.right,
bottom: rect.bottom
},
lineColor: bgrColor,
lineWidth: 2
},
// 左边框
{
rect: {
left: rect.left,
top: rect.top,
right: rect.left + 2,
bottom: rect.bottom
},
lineColor: bgrColor,
lineWidth: 2
},
// 右边框
{
rect: {
left: rect.right - 2,
top: rect.top,
right: rect.right,
bottom: rect.bottom
},
lineColor: bgrColor,
lineWidth: 2
}
];
for (const border of borders) {
const lineAnnotation: pdf.Pdf.LineAnnotationInfo = {
rect: border.rect,
lineColor: border.lineColor,
lineWidth: border.lineWidth
};
await pdfPage.addAnnotation(lineAnnotation);
}
}
/**
* 添加文本标注
*/
private async addTextAnnotation(
pdfPage: pdf.Pdf.PdfPage,
rect: pdf.Pdf.Rect,
content: string
): Promise<void> {
const textAnnotationInfo: pdf.Pdf.TextAnnotationInfo = {
rect: {
left: rect.left + 5,
top: rect.top + 5,
right: rect.right - 5,
bottom: rect.top + 25
},
content: content,
textColor: 0xFF000000, // 黑色文字
fontSize: 12
};
await pdfPage.addAnnotation(textAnnotationInfo);
}
/**
* ARGB转BGR
* 注意:LineAnnotationInfo的lineColor使用的是BGR格式
*/
private argbToBgr(argb: number): number {
// ARGB: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
// BGR: BBBBBBBBGGGGGGGGRRRRRRRR
const a = (argb >> 24) & 0xFF;
const r = (argb >> 16) & 0xFF;
const g = (argb >> 8) & 0xFF;
const b = argb & 0xFF;
// 转换为BGR格式,保留Alpha通道
return (a << 24) | (r << 16) | (g << 8) | b;
}
}
四、在AI旅行助手中的完整应用
4.1 PDF攻略标注功能实现
@Entry
@Component
struct TravelGuidePdfViewer {
private pdfManager: TravelGuideAnnotationManager = new TravelGuideAnnotationManager();
@State isPdfLoaded: boolean = false;
@State annotations: Array<any> = [];
@State showColorPicker: boolean = false;
@State selectedColor: number = 0xFFFF0000; // 默认红色
/**
* 加载PDF攻略
*/
async loadTravelGuidePdf(): Promise<void> {
try {
// 从资源文件加载PDF
const context = getContext(this) as common.UIAbilityContext;
const filePath = await this.copyPdfFromResource();
await this.pdfManager.loadPdfDocument(filePath);
this.isPdfLoaded = true;
prompt.showToast({
message: '旅行攻略PDF加载成功',
duration: 2000
});
} catch (error) {
console.error('加载PDF失败:', error);
prompt.showToast({
message: '加载失败,请重试',
duration: 2000
});
}
}
/**
* 从资源文件复制PDF
*/
private async copyPdfFromResource(): Promise<string> {
const context = getContext(this) as common.UIAbilityContext;
const filesDir = context.filesDir;
const destPath = `${filesDir}/travel_guide.pdf`;
// 检查文件是否已存在
try {
await fs.access(destPath);
return destPath;
} catch {
// 文件不存在,从资源复制
}
// 从rawfile复制
const resourceManager = context.resourceManager;
const rawFileDescriptor = await resourceManager.getRawFd('travel_guide.pdf');
const srcPath = `resources/rawfile/travel_guide.pdf`;
await fs.copyFile(srcPath, destPath);
return destPath;
}
/**
* 添加景点标注
*/
async addScenicSpotAnnotation(): Promise<void> {
if (!this.isPdfLoaded) {
return;
}
const rect: pdf.Pdf.Rect = {
left: 100,
top: 150,
right: 250,
bottom: 200
};
try {
await this.pdfManager.addTravelGuideAnnotation(
'SCENIC_SPOT',
rect,
'故宫博物院 - 建议游览时间:3-4小时'
);
this.annotations.push({
type: 'SCENIC_SPOT',
rect: rect,
content: '故宫博物院标注'
});
prompt.showToast({
message: '景点标注添加成功',
duration: 1000
});
} catch (error) {
console.error('添加标注失败:', error);
}
}
/**
* 添加餐厅标注
*/
async addRestaurantAnnotation(): Promise<void> {
if (!this.isPdfLoaded) {
return;
}
const rect: pdf.Pdf.Rect = {
left: 100,
top: 220,
right: 250,
bottom: 270
};
try {
await this.pdfManager.addTravelGuideAnnotation(
'RESTAURANT',
rect,
'全聚德烤鸭店 - 推荐:烤鸭、鸭汤'
);
this.annotations.push({
type: 'RESTAURANT',
rect: rect,
content: '餐厅标注'
});
prompt.showToast({
message: '餐厅标注添加成功',
duration: 1000
});
} catch (error) {
console.error('添加标注失败:', error);
}
}
build() {
Column({ space: 20 }) {
// 标题
Text('旅行攻略标注工具')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 30, bottom: 20 })
// PDF显示区域
if (this.isPdfLoaded) {
Column({ space: 10 }) {
Text('PDF加载成功')
.fontSize(16)
.fontColor('#52c41a')
// 这里应该是PDF预览组件
// 由于PDF预览需要特定组件,这里用占位图代替
Image($r('app.media.pdf_preview'))
.width('90%')
.height(400)
.objectFit(ImageFit.Contain)
.backgroundColor('#f0f0f0')
.borderRadius(8)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
} else {
Column({ space: 10 }) {
Image($r('app.media.pdf_icon'))
.width(100)
.height(100)
.margin({ bottom: 20 })
Text('点击加载旅行攻略PDF')
.fontSize(16)
.fontColor('#666666')
}
.width('100%')
.height(400)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => this.loadTravelGuidePdf())
}
// 标注工具区域
Column({ space: 15 }) {
Text('标注工具')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.width('100%')
.textAlign(TextAlign.Start)
.margin({ left: 20 })
Row({ space: 15 }) {
// 景点标注按钮
Button('添加景点标注')
.onClick(() => this.addScenicSpotAnnotation())
.backgroundColor('#ff4d4f')
.fontColor(Color.White)
.width(120)
.height(40)
// 餐厅标注按钮
Button('添加餐厅标注')
.onClick(() => this.addRestaurantAnnotation())
.backgroundColor('#52c41a')
.fontColor(Color.White)
.width(120)
.height(40)
// 颜色选择器
Button('选择颜色')
.onClick(() => {
this.showColorPicker = !this.showColorPicker;
})
.backgroundColor('#1890ff')
.fontColor(Color.White)
.width(100)
.height(40)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(10)
// 颜色选择器
if (this.showColorPicker) {
Column({ space: 10 }) {
Text('选择标注颜色')
.fontSize(14)
.fontColor('#666666')
Row({ space: 10 }) {
// 红色
Circle()
.width(30)
.height(30)
.fill('#ff4d4f')
.onClick(() => {
this.selectedColor = 0xFFFF0000;
this.showColorPicker = false;
})
// 绿色
Circle()
.width(30)
.height(30)
.fill('#52c41a')
.onClick(() => {
this.selectedColor = 0xFF00FF00;
this.showColorPicker = false;
})
// 黄色
Circle()
.width(30)
.height(30)
.fill('#fadb14')
.onClick(() => {
this.selectedColor = 0xFFFFFF00;
this.showColorPicker = false;
})
// 蓝色
Circle()
.width(30)
.height(30)
.fill('#1890ff')
.onClick(() => {
this.selectedColor = 0xFF0000FF;
this.showColorPicker = false;
})
}
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
.border({ width: 1, color: '#e8e8e8' })
.borderRadius(8)
.margin({ top: 10 })
}
}
.width('100%')
.padding(20)
.backgroundColor('#fafafa')
.borderRadius(12)
.margin({ left: 20, right: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
}
}
4.2 标注管理功能
/**
* PDF标注管理器增强版
*/
class EnhancedPdfAnnotationManager extends PdfAnnotationManager {
private annotations: Map<string, pdf.Pdf.PdfAnnotation> = new Map();
private annotationStyles: Map<string, AnnotationStyle> = new Map();
/**
* 标注样式定义
*/
private defineAnnotationStyles(): void {
this.annotationStyles.set('HIGHLIGHT', {
fillColor: 0x30FFD700, // 黄色高亮,30%透明度
borderColor: 0xFFFFD700,
borderWidth: 1,
borderStyle: pdf.Pdf.LineDashStyle.SOLID
});
this.annotationStyles.set('COMMENT', {
fillColor: 0x30009688, // 青色注释
borderColor: 0xFF009688,
borderWidth: 1,
borderStyle: pdf.Pdf.LineDashStyle.DASHED
});
this.annotationStyles.set('IMPORTANT', {
fillColor: 0x30FF5252, // 红色重要
borderColor: 0xFFFF5252,
borderWidth: 2,
borderStyle: pdf.Pdf.LineDashStyle.SOLID
});
}
/**
* 添加智能标注
*/
async addSmartAnnotation(
type: string,
rect: pdf.Pdf.Rect,
content: string
): Promise<string> {
const style = this.annotationStyles.get(type) || this.annotationStyles.get('COMMENT')!;
const pdfBorder: pdf.Pdf.PdfBorder = {
fillColor: style.fillColor,
borderColor: style.borderColor,
borderWidth: style.borderWidth,
borderStyle: style.borderStyle
};
const squareAnnotationInfo: pdf.Pdf.SquareAnnotationInfo = {
rect: rect,
content: content,
border: pdfBorder,
opacity: 0.3
};
const pdfPage = await this.getCurrentPage();
const annotation = await pdfPage.addAnnotation(squareAnnotationInfo);
const annotationId = annotation.getAnnotationId();
this.annotations.set(annotationId, annotation);
return annotationId;
}
/**
* 删除标注
*/
async removeAnnotation(annotationId: string): Promise<boolean> {
const annotation = this.annotations.get(annotationId);
if (!annotation) {
return false;
}
try {
await annotation.delete();
this.annotations.delete(annotationId);
return true;
} catch (error) {
console.error('删除标注失败:', error);
return false;
}
}
/**
* 修改标注样式
*/
async updateAnnotationStyle(
annotationId: string,
newStyle: AnnotationStyle
): Promise<boolean> {
const annotation = this.annotations.get(annotationId);
if (!annotation) {
return false;
}
try {
// 先删除旧标注
await annotation.delete();
// 重新添加新样式的标注
const rect = await annotation.getRect();
const content = await annotation.getContent();
const pdfBorder: pdf.Pdf.PdfBorder = {
fillColor: newStyle.fillColor,
borderColor: newStyle.borderColor,
borderWidth: newStyle.borderWidth,
borderStyle: newStyle.borderStyle
};
const squareAnnotationInfo: pdf.Pdf.SquareAnnotationInfo = {
rect: rect,
content: content,
border: pdfBorder,
opacity: 0.3
};
const pdfPage = await this.getCurrentPage();
const newAnnotation = await pdfPage.addAnnotation(squareAnnotationInfo);
this.annotations.set(annotationId, newAnnotation);
return true;
} catch (error) {
console.error('更新标注样式失败:', error);
return false;
}
}
/**
* 导出带标注的PDF
*/
async exportAnnotatedPdf(): Promise<string> {
if (!this.pdfDocument) {
throw new Error('PDF文档未加载');
}
const timestamp = new Date().getTime();
const exportPath = `/data/storage/el2/base/haps/entry/files/annotated_${timestamp}.pdf`;
try {
await this.pdfDocument.writeFile(exportPath);
return exportPath;
} catch (error) {
console.error('导出PDF失败:', error);
throw new Error('导出失败');
}
}
}
五、解决方案对比与选择建议
5.1 方案对比
|
特性 |
方案一:PdfBorder方案 |
方案二:手动绘制方案 |
|---|---|---|
|
实现复杂度 |
简单,直接使用API |
复杂,需要计算坐标和手动绘制 |
|
性能 |
优秀,原生API支持 |
一般,需要多次绘制操作 |
|
兼容性 |
高,官方推荐方案 |
高,完全可控 |
|
颜色准确性 |
高,直接设置颜色值 |
注意BGR格式转换 |
|
维护成本 |
低 |
高 |
|
扩展性 |
一般 |
高,可自定义各种形状 |
5.2 选择建议
推荐使用方案一(PdfBorder方案)的情况:
-
对性能要求较高
-
需要快速实现基本功能
-
项目时间紧张
-
标注样式相对固定
推荐使用方案二(手动绘制方案)的情况:
-
需要特殊形状的标注
-
对标注样式有特殊要求
-
方案一在某些设备上不兼容
-
需要高度自定义的标注效果
5.3 最佳实践建议
-
先尝试方案一:在大多数情况下,方案一都能正常工作且效果更好
-
做好兼容性测试:在不同设备和系统版本上测试标注效果
-
提供用户反馈:当标注不生效时,给用户明确的提示
-
实现降级方案:当方案一失败时,自动降级到方案二
-
颜色值验证:确保颜色值的格式正确(ARGB或BGR)
六、常见问题与解决方案
6.1 问题:颜色显示不正确
解决方案:
// 颜色格式转换工具函数
class ColorUtils {
/**
* 验证并修复颜色值
*/
static normalizeColor(color: number, format: 'ARGB' | 'BGR' = 'ARGB'): number {
// 确保Alpha通道不为0
let alpha = (color >> 24) & 0xFF;
if (alpha === 0) {
alpha = 0xFF; // 默认不透明
}
const red = (color >> 16) & 0xFF;
const green = (color >> 8) & 0xFF;
const blue = color & 0xFF;
if (format === 'BGR') {
// 转换为BGR格式
return (alpha << 24) | (blue << 16) | (green << 8) | red;
} else {
// 保持ARGB格式
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
}
/**
* 创建带透明度的颜色
*/
static withAlpha(color: number, alpha: number): number {
// alpha: 0-255
const red = (color >> 16) & 0xFF;
const green = (color >> 8) & 0xFF;
const blue = color & 0xFF;
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
}
6.2 问题:标注位置计算
解决方案:
/**
* 坐标计算工具
*/
class CoordinateUtils {
/**
* 根据百分比计算绝对坐标
*/
static percentToAbsolute(
percentRect: { left: number; top: number; right: number; bottom: number },
pageWidth: number,
pageHeight: number
): pdf.Pdf.Rect {
return {
left: (percentRect.left / 100) * pageWidth,
top: (percentRect.top / 100) * pageHeight,
right: (percentRect.right / 100) * pageWidth,
bottom: (percentRect.bottom / 100) * pageHeight
};
}
/**
* 确保坐标在页面范围内
*/
static clampRect(rect: pdf.Pdf.Rect, pageWidth: number, pageHeight: number): pdf.Pdf.Rect {
return {
left: Math.max(0, Math.min(rect.left, pageWidth)),
top: Math.max(0, Math.min(rect.top, pageHeight)),
right: Math.max(rect.left, Math.min(rect.right, pageWidth)),
bottom: Math.max(rect.top, Math.min(rect.bottom, pageHeight))
};
}
}
七、在AI旅行助手中的完整工作流
7.1 标注工作流程
// 完整的旅行攻略标注工作流
class TravelGuideAnnotationWorkflow {
private pdfManager: EnhancedPdfAnnotationManager;
private currentGuide: TravelGuide | null = null;
/**
* 初始化工作流
*/
constructor() {
this.pdfManager = new EnhancedPdfAnnotationManager();
this.pdfManager.defineAnnotationStyles();
}
/**
* 加载旅行攻略并自动添加标注
*/
async loadAndAnnotateGuide(guideId: string): Promise<void> {
try {
// 1. 加载旅行攻略
this.currentGuide = await this.loadTravelGuide(guideId);
// 2. 生成PDF
const pdfPath = await this.generatePdfFromGuide(this.currentGuide);
// 3. 加载PDF
await this.pdfManager.loadPdfDocument(pdfPath);
// 4. 自动添加智能标注
await this.addAutoAnnotations(this.currentGuide);
// 5. 显示标注工具
this.showAnnotationTools();
} catch (error) {
console.error('攻略标注工作流失败:', error);
throw error;
}
}
/**
* 自动添加智能标注
*/
private async addAutoAnnotations(guide: TravelGuide): Promise<void> {
const annotations = this.extractAnnotationsFromGuide(guide);
for (const annotation of annotations) {
await this.pdfManager.addSmartAnnotation(
annotation.type,
annotation.rect,
annotation.content
);
}
}
/**
* 从攻略中提取标注信息
*/
private extractAnnotationsFromGuide(guide: TravelGuide): Array<{
type: string;
rect: pdf.Pdf.Rect;
content: string;
}> {
const annotations = [];
// 提取景点信息
for (const scenicSpot of guide.scenicSpots) {
annotations.push({
type: 'SCENIC_SPOT',
rect: this.calculateRectForContent(scenicSpot.description),
content: `景点:${scenicSpot.name}\n建议:${scenicSpot.tips}`
});
}
// 提取餐厅信息
for (const restaurant of guide.restaurants) {
annotations.push({
type: 'RESTAURANT',
rect: this.calculateRectForContent(restaurant.description),
content: `餐厅:${restaurant.name}\n推荐:${restaurant.recommendations.join('、')}`
});
}
// 提取重要提示
for (const tip of guide.importantTips) {
annotations.push({
type: 'IMPORTANT',
rect: this.calculateRectForContent(tip),
content: `重要提示:${tip}`
});
}
return annotations;
}
}
八、总结
通过本文的详细解析,我们解决了HarmonyOS 6中PDF标注功能的关键问题,并提供了两种可行的解决方案。在AI旅行助手项目中,这些技术可以帮助用户:
-
高效标注旅行攻略:快速标记重要景点、餐厅和注意事项
-
颜色分类管理:通过不同颜色区分不同类型的标注
-
智能标注建议:基于AI分析自动推荐标注位置
-
便捷分享:导出带标注的PDF与朋友分享
关键收获:
-
PDF标注的
fillColor问题可以通过PdfBorder属性解决 -
对于特殊需求,可以使用
LineAnnotationInfo手动绘制 -
颜色格式转换是关键,特别是BGR与ARGB的区别
-
在实际应用中要考虑用户体验和性能平衡
最佳实践建议:
-
始终优先使用方案一,在必要时降级到方案二
-
提供颜色选择器,让用户自定义标注颜色
-
实现标注的保存、加载和分享功能
-
考虑多端适配,确保在手机、平板、PC上都有良好体验
通过这些技术的应用,AI旅行助手的PDF标注功能不仅解决了颜色显示问题,还为用户提供了更加丰富和实用的标注体验,让旅行攻略的阅读和分享变得更加高效和有趣。
更多推荐

所有评论(0)