ส่วนใหญ่ของคำตอบสำหรับคำถามนี้เป็นอายุห้าปี, รองรับเฉพาะ PDF, และเริ่มด้วยการให้คุณวัดพิกัดเทียบกับหน้าที่คุณมองไม่เห็น นี่คือเวอร์ชันปี 2026: เจ็ดการเรียก, สองแนวทางที่กำหนดว่ามาร์กอัปจะอยู่ที่ไหน, และบรรทัดเดียวที่คอนเทนเนอร์ Linux ต้องการ
ทุกอย่างด้านล่างทำงานบน .NET 8 และ .NET 10, บน Windows และ Linux, โดยไม่ต้องติดตั้ง Adobe Acrobat หรือ Microsoft Office — และโค้ดเดียวกันทำงานกับ DOCX, XLSX, PPTX และรูปภาพ เพียงแค่เส้นทางไฟล์ที่เปลี่ยนไป
เวอร์ชันที่สามารถรันได้อยู่บน GitHub:
annotate-pdf-csharp-net.
การติดตั้ง
dotnet add package GroupDocs.Annotation
หากไม่มีลิขสิทธิ์ ไลบรารีจะทำงานในโหมดประเมินผลและผลลัพธ์จะมีเครื่องหมายการประเมิน การลิขสิทธิ์ชั่วคราวจะลบเครื่องหมายเหล่านั้นออก:
new License().SetLicense("GroupDocs.Annotation.lic");
เปิดเอกสาร
using (Annotator annotator = new Annotator("quarterly-review.pdf"))
{
// add, read or remove markup here
}
สิ่งหนึ่งที่ควรทราบตั้งแต่แรก: Annotator จะเปิดไฟล์ไว้จนกว่าจะถูกทำลาย, และไม่สามารถบันทึกทับไฟล์ที่กำลังอ่านได้ ให้บันทึกไปยังเส้นทางอื่น, หรือไฟล์ชั่วคราวที่คุณย้ายไปยังตำแหน่งเดิมภายหลัง.
อย่าวัดพิกัด — ค้นหาข้อความ
นี่คือส่วนที่บทแนะนำส่วนใหญ่มักข้าม, และเป็นส่วนที่ทำให้โค้ด annotation อยู่รอดในเวอร์ชันถัดไปของเอกสาร GetDocumentInfo() จะคืนค่าทุกหน้าพร้อมขนาดและบรรทัดข้อความของมัน, แต่ละบรรทัดมีกล่อง:
IDocumentInfo info = annotator.Document.GetDocumentInfo();
foreach (PageInfo page in info.PagesInfo)
{
foreach (TextLineInfo line in page.TextLines)
{
if (line.Text.Contains("Hosting costs"))
{
// line.LeftIndent, line.TopIndent, line.Width, line.Height — points, from the top-left
}
}
}
ตอนนี้ annotation สามารถวางบนสิ่งที่เอกสารบอกวันนี้, แทนที่จะวางบนตำแหน่งที่มันบอกเมื่อปีที่แล้ว.
เพิ่มไฮไลท์พร้อมคอมเมนต์และการตอบกลับ
float left = (float)line.LeftIndent;
float right = (float)(line.LeftIndent + line.Width);
float top = (float)(page.Height - line.TopIndent);
float bottom = (float)(page.Height - line.TopIndent - line.Height);
annotator.Add(new HighlightAnnotation
{
PageNumber = page.PageNumber - 1,
BackgroundColor = 0xFFE699,
Opacity = 0.5,
Message = "Check this against the invoice",
CreatedOn = DateTime.Now,
User = new User { Id = 1, Name = "Reviewer", Role = Role.Editor },
Points = new List<Point>
{
new Point(left, top), new Point(right, top),
new Point(left, bottom), new Point(right, bottom)
},
Replies = new List<Reply>
{
new Reply { Comment = "Confirmed with finance.", RepliedOn = DateTime.Now }
}
});
annotator.Save("result.pdf");
Message คือคอมเมนต์. Replies เริ่มเธรด — ในโปรแกรมอ่าน PDF จะเป็นโน้ตพร้อมการตอบ, ใน Word จะเป็นคอมเมนต์พร้อมการตอบกลับด้านล่าง. User คือสิ่งที่ทำให้ขั้นตอนต่อมาสามารถค้นหาและลบ markup ของผู้ตรวจสอบคนนี้ได้โดยตรงและไม่กระทบ markup ของคนอื่น.
สองแนวทางที่ครอบคลุมทุกคน
ดูอย่างใกล้ชิดที่สองบรรทัดที่คำนวณ top และ bottom, และที่ PageNumber.
หมายเลขหน้าได้รับการนับสองครั้ง. PageInfo.PageNumber เป็น 1-based. AnnotationBase.PageNumber เป็น 0-based. หากผสมกัน ทุก annotation จะชี้หน้าไปผิดหนึ่งหน้า — และ annotation ที่ตั้งไว้สำหรับหน้าสุดท้ายจะหายไป, เนื่องจาก annotation ที่อ้างอิงหน้าที่ไม่มีอยู่จะถูกละทิ้งเมื่อบันทึก, โดยไม่มีข้อยกเว้นหรือคำเตือน หาก markup “ไม่ปรากฏ”, ให้ตรวจสอบดัชนีหน้าก่อนอื่นใด.
สองจุดอ้างอิง. TextLineInfo และ AreaAnnotation.Box วัดจากมุม บนซ้าย. การทำ markup ของข้อความ — ไฮไลท์, ขีดเส้นใต้, ขีดฆ่า, เส้นโค้ง — วัดจาก ล่างซ้าย, ซึ่งเป็นวิธีที่ PDF ทำงาน.
| Member | Origin | Page numbering |
|---|---|---|
PageInfo, TextLineInfo |
บนซ้าย | เริ่มจาก 1 |
AnnotationBase.PageNumber |
— | เริ่มจาก 0 |
AreaAnnotation.Box |
บนซ้าย | — |
HighlightAnnotation.Points |
ล่างซ้าย | — |
PreviewOptions.PageNumbers |
— | เริ่มจาก 1 |
สี่จุดสร้างสี่เหลี่ยมหนึ่งรูป, ตามลำดับนี้: บนซ้าย, บนขวา, ล่างซ้าย, ล่างขวา. สามารถต่อสี่เหลี่ยมเพิ่มเติมในรายการเดียวกัน, ซึ่งเป็นวิธีที่คอมเมนต์เดียวครอบคลุมประโยคที่ตัดบรรทัดหลายบรรทัด.
อ่าน markup กลับ
using (Annotator annotator = new Annotator("result.pdf"))
{
foreach (AnnotationBase a in annotator.Get())
{
Console.WriteLine($"#{a.Id} {a.Type} on page {a.PageNumber + 1}: {a.Message}");
foreach (Reply reply in a.Replies)
Console.WriteLine($" reply: {reply.Comment}");
}
}
Get() อ่านสิ่งที่เอกสารมีจริง — รวมถึง annotation ที่คนเพิ่มใน Word หรือ Acrobat, ซึ่งทำให้การเดินทางรอบผ่านเครื่องมืออื่นทำงานได้.
ข้อควรระวังหนึ่งที่ควรทราบ: Id ถูกกำหนดขณะอ่านเอกสาร, ไม่ได้ถูกเก็บไว้ในเอกสาร. มันค่อนข้างเสถียรพอที่จะลบหรืออัปเดต annotation ในเซสชันเดียวกัน, และไม่ใช่คีย์สำหรับใส่ในฐานข้อมูล. หากต้องการเก็บ annotation นอกไฟล์, ใช้การนำเข้าและส่งออก.
ลบมันออกอีกครั้ง
annotator.Remove(1); // by id
annotator.Remove(annotator.Get()
.Where(a => a.User?.Name == "Reviewer").ToList()); // or by author
แสดงผลหน้า
PreviewOptions previewOptions = new PreviewOptions(n => File.Create($"page_{n}.png"));
previewOptions.PreviewFormat = PreviewFormats.PNG;
previewOptions.PageNumbers = new[] { 1, 2 }; // 1-based
previewOptions.RenderAnnotations = true;
previewOptions.Resolution = 144;
annotator.Document.GeneratePreview(previewOptions);
มีประโยชน์ในสามสถานการณ์: UI เว็บที่แสดงเอกสาร, การทดสอบที่พิสูจน์ว่า markup ลงบนตำแหน่งที่คุณตั้งใจ, และเอเจนต์ที่ต้องตรวจสอบงานของตนเอง.
บน Linux
โค้ดไม่เปลี่ยนแปลง. คอนเทนเนอร์ทำการเปลี่ยนแปลง:
FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN apt-get update \
&& apt-get install -y --no-install-recommends fontconfig fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
.NET runtime images มาพร้อมไม่มีฟอนต์และการเรนเดอร์ข้อความต้องการฟอนต์บรรทัดเดียวนี้เป็นความแตกต่างระหว่างผลลัพธ์ที่ถูกต้องและหน้าว่าง — และเป็นคำถาม Linux ที่พบบ่อยที่สุดเกี่ยวกับไลบรารีเอกสารโดยทั่วไป.
ขั้นตอนต่อไป
git clone https://github.com/groupdocs-annotation/annotate-pdf-csharp-net.git
cd annotate-pdf-csharp-net
dotnet run --project src/AnnotatePdf -- highlight samples/quarterly-review.pdf --text "Hosting costs"
- เอกสาร: อธิบายการทำ Annotation PDF ด้วย C#
- ให้ LLM ตรวจสอบสัญญาและทิ้งคอมเมนต์จริง — การเรียกเดียวกัน, ขับเคลื่อนโดยโมเดล
- สร้าง UI การทำ annotation บนเบราว์เซอร์ — การเรียกเดียวกัน, ขับเคลื่อนโดยผู้ใช้ในเบราว์เซอร์