Annotations - PyMuPDF 1.24.4 documentation (2024)

English日本語

How to Add and Modify Annotations#

In PyMuPDF, new annotations can be added via Page methods. Once an annotation exists, it can be modified to a large extent using methods of the Annot class.

Annotations can only be inserted in PDF pages - other document types do not support annotation insertion.

In contrast to many other tools, initial insert of annotations happens with a minimum number of properties. We leave it to the programmer to e.g. set attributes like author, creation date or subject.

As an overview for these capabilities, look at the following script that fills a PDF page with most of the available annotations. Look in the next sections for more special situations:

# -*- coding: utf-8 -*-"""-------------------------------------------------------------------------------Demo script showing how annotations can be added to a PDF using PyMuPDF.It contains the following annotation types:Caret, Text, FreeText, text markers (underline, strike-out, highlight,squiggle), Circle, Square, Line, PolyLine, Polygon, FileAttachment, Stampand Redaction.There is some effort to vary appearances by adding colors, line ends,opacity, rotation, dashed lines, etc.Dependencies------------PyMuPDF v1.17.0-------------------------------------------------------------------------------"""from __future__ import print_functionimport gcimport sysimport pymupdfprint(pymupdf.__doc__)if pymupdf.VersionBind.split(".") < ["1", "17", "0"]: sys.exit("PyMuPDF v1.17.0+ is needed.")gc.set_debug(gc.DEBUG_UNCOLLECTABLE)highlight = "this text is highlighted"underline = "this text is underlined"strikeout = "this text is striked out"squiggled = "this text is zigzag-underlined"red = (1, 0, 0)blue = (0, 0, 1)gold = (1, 1, 0)green = (0, 1, 0)displ = pymupdf.Rect(0, 50, 0, 50)r = pymupdf.Rect(72, 72, 220, 100)t1 = u"têxt üsès Lätiñ charß,\nEUR: €, mu: µ, super scripts: ²³!"def print_descr(annot): """Print a short description to the right of each annot rect.""" annot.parent.insert_text( annot.rect.br + (10, -5), "%s annotation" % annot.type[1], color=red )doc = pymupdf.open()page = doc.new_page()page.set_rotation(0)annot = page.add_caret_annot(r.tl)print_descr(annot)r = r + displannot = page.add_freetext_annot( r, t1, fontsize=10, rotate=90, text_color=blue, fill_color=gold, align=pymupdf.TEXT_ALIGN_CENTER,)annot.set_border(width=0.3, dashes=[2])annot.update(text_color=blue, fill_color=gold)print_descr(annot)r = annot.rect + displannot = page.add_text_annot(r.tl, t1)print_descr(annot)# Adding text marker annotations:# first insert a unique text, then search for it, then mark itpos = annot.rect.tl + displ.tlpage.insert_text( pos, # insertion point highlight, # inserted text morph=(pos, pymupdf.Matrix(-5)), # rotate around insertion point)rl = page.search_for(highlight, quads=True) # need a quad b/o tilted textannot = page.add_highlight_annot(rl[0])print_descr(annot)pos = annot.rect.bl # next insertion pointpage.insert_text(pos, underline, morph=(pos, pymupdf.Matrix(-10)))rl = page.search_for(underline, quads=True)annot = page.add_underline_annot(rl[0])print_descr(annot)pos = annot.rect.blpage.insert_text(pos, strikeout, morph=(pos, pymupdf.Matrix(-15)))rl = page.search_for(strikeout, quads=True)annot = page.add_strikeout_annot(rl[0])print_descr(annot)pos = annot.rect.blpage.insert_text(pos, squiggled, morph=(pos, pymupdf.Matrix(-20)))rl = page.search_for(squiggled, quads=True)annot = page.add_squiggly_annot(rl[0])print_descr(annot)pos = annot.rect.blr = pymupdf.Rect(pos, pos.x + 75, pos.y + 35) + (0, 20, 0, 20)annot = page.add_polyline_annot([r.bl, r.tr, r.br, r.tl]) # 'Polyline'annot.set_border(width=0.3, dashes=[2])annot.set_colors(stroke=blue, fill=green)annot.set_line_ends(pymupdf.PDF_ANNOT_LE_CLOSED_ARROW, pymupdf.PDF_ANNOT_LE_R_CLOSED_ARROW)annot.update(fill_color=(1, 1, 0))print_descr(annot)r += displannot = page.add_polygon_annot([r.bl, r.tr, r.br, r.tl]) # 'Polygon'annot.set_border(width=0.3, dashes=[2])annot.set_colors(stroke=blue, fill=gold)annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)annot.update()print_descr(annot)r += displannot = page.add_line_annot(r.tr, r.bl) # 'Line'annot.set_border(width=0.3, dashes=[2])annot.set_colors(stroke=blue, fill=gold)annot.set_line_ends(pymupdf.PDF_ANNOT_LE_DIAMOND, pymupdf.PDF_ANNOT_LE_CIRCLE)annot.update()print_descr(annot)r += displannot = page.add_rect_annot(r) # 'Square'annot.set_border(width=1, dashes=[1, 2])annot.set_colors(stroke=blue, fill=gold)annot.update(opacity=0.5)print_descr(annot)r += displannot = page.add_circle_annot(r) # 'Circle'annot.set_border(width=0.3, dashes=[2])annot.set_colors(stroke=blue, fill=gold)annot.update()print_descr(annot)r += displannot = page.add_file_annot( r.tl, b"just anything for testing", "testdata.txt" # 'FileAttachment')print_descr(annot) # annot.rectr += displannot = page.add_stamp_annot(r, stamp=10) # 'Stamp'annot.set_colors(stroke=green)annot.update()print_descr(annot)r += displ + (0, 0, 50, 10)rc = page.insert_textbox( r, "This content will be removed upon applying the redaction.", color=blue, align=pymupdf.TEXT_ALIGN_CENTER,)annot = page.add_redact_annot(r)print_descr(annot)doc.save(__file__.replace(".py", "-%i.pdf" % page.rotation), deflate=True)

This script should lead to the following output:

How to Use FreeText#

This script shows a couple of ways to deal with ‘FreeText’ annotations:

# -*- coding: utf-8 -*-import pymupdf# some colorsblue = (0,0,1)green = (0,1,0)red = (1,0,0)gold = (1,1,0)# a new PDF with 1 pagedoc = pymupdf.open()page = doc.new_page()# 3 rectangles, same size, above each otherr1 = pymupdf.Rect(100,100,200,150)r2 = r1 + (0,75,0,75)r3 = r2 + (0,75,0,75)# the text, Latin alphabett = "¡Un pequeño texto para practicar!"# add 3 annots, modify the last one somewhata1 = page.add_freetext_annot(r1, t, color=red)a2 = page.add_freetext_annot(r2, t, fontname="Ti", color=blue)a3 = page.add_freetext_annot(r3, t, fontname="Co", color=blue, rotate=90)a3.set_border(width=0)a3.update(fontsize=8, fill_color=gold)# save the PDFdoc.save("a-freetext.pdf")

The result looks like this:

Using Buttons and JavaScript#

Since MuPDF v1.16, ‘FreeText’ annotations no longer support bold or italic versions of the Times-Roman, Helvetica or Courier fonts.

A big thank you to our user @kurokawaikki, who contributed the following script to circumvent this restriction.

"""Problem: Since MuPDF v1.16 a 'Freetext' annotation font is restricted to the"normal" versions (no bold, no italics) of Times-Roman, Helvetica, Courier.It is impossible to use PyMuPDF to modify this.Solution: Using Adobe's JavaScript API, it is possible to manipulate propertiesof Freetext annotations. Check out these references:https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf,or https://www.adobe.com/devnet/acrobat/documentation.html.Function 'this.getAnnots()' will return all annotations as an array. We loopover this array to set the properties of the text through the 'richContents'attribute.There is no explicit property to set text to bold, but it is possible to setfontWeight=800 (400 is the normal size) of richContents.Other attributes, like color, italics, etc. can also be set via richContents.If we have 'FreeText' annotations created with PyMuPDF, we can make use of thisJavaScript feature to modify the font - thus circumventing the above restriction.Use PyMuPDF v1.16.12 to create a push button that executes a Javascriptcontaining the desired code. This is what this program does.Then open the resulting file with Adobe reader (!).After clicking on the button, all Freetext annotations will be bold, and thefile can be saved.If desired, the button can be removed again, using free tools like PyMuPDF orPDF XChange editor.Note / Caution:---------------The JavaScript will **only** work if the file is opened with Adobe Acrobat reader!When using other PDF viewers, the reaction is unforeseeable."""import sysimport pymupdf# this JavaScript will execute when the button is clicked:jscript = """var annt = this.getAnnots();annt.forEach(function (item, index) { try { var span = item.richContents; span.forEach(function (it, dx) { it.fontWeight = 800; }) item.richContents = span; } catch (err) {}});app.alert('Done');"""i_fn = sys.argv[1] # input file nameo_fn = "bold-" + i_fn # output filenamedoc = pymupdf.open(i_fn) # open inputpage = doc[0] # get desired page# ------------------------------------------------# make a push button for invoking the JavaScript# ------------------------------------------------widget = pymupdf.Widget() # create widget# make it a 'PushButton'widget.field_type = pymupdf.PDF_WIDGET_TYPE_BUTTONwidget.field_flags = pymupdf.PDF_BTN_FIELD_IS_PUSHBUTTONwidget.rect = pymupdf.Rect(5, 5, 20, 20) # button positionwidget.script = jscript # fill in JavaScript source textwidget.field_name = "Make bold" # arbitrary namewidget.field_value = "Off" # arbitrary valuewidget.fill_color = (0, 0, 1) # make button visibleannot = page.add_widget(widget) # add the widget to the pagedoc.save(o_fn) # output the file

How to Use Ink Annotations#

Ink annotations are used to contain freehand scribbling. A typical example may be an image of your signature consisting of first name and last name. Technically an ink annotation is implemented as a list of lists of points. Each point list is regarded as a continuous line connecting the points. Different point lists represent independent line segments of the annotation.

The following script creates an ink annotation with two mathematical curves (sine and cosine function graphs) as line segments:

import mathimport pymupdf#------------------------------------------------------------------------------# preliminary stuff: create function value lists for sine and cosine#------------------------------------------------------------------------------w360 = math.pi * 2 # go through full circledeg = w360 / 360 # 1 degree as radiansrect = pymupdf.Rect(100,200, 300, 300) # use this rectanglefirst_x = rect.x0 # x starts from leftfirst_y = rect.y0 + rect.height / 2. # rect middle means y = 0x_step = rect.width / 360 # rect width means 360 degreesy_scale = rect.height / 2. # rect height means 2sin_points = [] # sine values go herecos_points = [] # cosine values go herefor x in range(362): # now fill in the values x_coord = x * x_step + first_x # current x coordinate y = -math.sin(x * deg) # sine p = (x_coord, y * y_scale + first_y) # corresponding point sin_points.append(p) # append y = -math.cos(x * deg) # cosine p = (x_coord, y * y_scale + first_y) # corresponding point cos_points.append(p) # append#------------------------------------------------------------------------------# create the document with one page#------------------------------------------------------------------------------doc = pymupdf.open() # make new PDFpage = doc.new_page() # give it a page#------------------------------------------------------------------------------# add the Ink annotation, consisting of 2 curve segments#------------------------------------------------------------------------------annot = page.addInkAnnot((sin_points, cos_points))# let it look a little nicerannot.set_border(width=0.3, dashes=[1,]) # line thickness, some dashingannot.set_colors(stroke=(0,0,1)) # make the lines blueannot.update() # update the appearancepage.draw_rect(rect, width=0.3) # only to demonstrate we did OKdoc.save("a-inktest.pdf")

This is the result:

This software is provided AS-IS with no warranty, either express or implied. This software is distributed under license and may not be copied, modified or distributed except as expressly authorized under the terms of that license. Refer to licensing information at artifex.com or contact Artifex Software Inc., 39 Mesa Street, Suite 108A, San Francisco CA 94129, United States for further information.

This documentation covers all versions up to 1.24.4.

Annotations - PyMuPDF 1.24.4 documentation (5)
Annotations - PyMuPDF 1.24.4 documentation (2024)

References

Top Articles
Latest Posts
Article information

Author: Eusebia Nader

Last Updated:

Views: 6378

Rating: 5 / 5 (80 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Eusebia Nader

Birthday: 1994-11-11

Address: Apt. 721 977 Ebert Meadows, Jereville, GA 73618-6603

Phone: +2316203969400

Job: International Farming Consultant

Hobby: Reading, Photography, Shooting, Singing, Magic, Kayaking, Mushroom hunting

Introduction: My name is Eusebia Nader, I am a encouraging, brainy, lively, nice, famous, healthy, clever person who loves writing and wants to share my knowledge and understanding with you.