[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XE26EM0Q4SBG5VCWGS7EXG":3,"skill-same:01M6XE26EM0Q4SBG5VCWGS7EXG":59},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":38,"commitSha":39,"skillDirectory":40,"forksCount":13,"repositoryHtmlUrl":41,"repositoryHomepageUrl":6,"ownerHtmlUrl":42,"ownerBlogUrl":6,"ownerDescription":43,"updatedAt":44,"install":45,"resources":58},"01M6XE26EM0Q4SBG5VCWGS7EXG","pdf-editing",null,"Complete guide for reading and editing PDF documents with PyMuPDF.","Waaaalnut\u002Fskillsbench","Waaaalnut","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F103995656?v=4","01M6XCS9K1V51EY3JMB7DGMX24","01M6XCS9Q4WPW88W62C55G1H44",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fedit-pdf\u002Fenvironment\u002Fskills\u002Fpdf-editing\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000002","文档处理","SCENARIO","使用场景",[23,24,25,26,27,28,29,30,31,32],"PDF","editing","pdf editing","documents","guide","complete","pymupdf","with","reading","for","2026-11-10 10:12:30",false,"---\nname: pdf-editing\ndescription: \"Complete guide for reading and editing PDF documents with PyMuPDF.\"\n---\n\n# PDF Editing Skill\n\n## CRITICAL RULES - READ FIRST\n\n**NEVER DO THESE:**\n- NEVER use strikethrough lines to cross out text\n- NEVER rasterize or flatten the PDF to images\n- NEVER convert PDF pages to PNG\u002FJPG and draw on them\n- NEVER use pdf-to-image-to-pdf workflows\n- NEVER use add_redact_annot() with BLACK fill (use WHITE fill instead)\n- NEVER add text NEXT TO old values - REPLACE them at the SAME position\n\n**TWO APPROACHES - CHOOSE THE RIGHT ONE:**\n\n1. **For REPLACING text** (e.g., updating name, email, DOB):\n   - Use `draw_rect()` with white fill to cover old text\n   - Use `insert_text()` at the SAME position\n   - Text layer is preserved\n\n2. **For TRUE REDACTION of sensitive data** (e.g., student ID):\n   - Use `add_redact_annot(rect, fill=(1,1,1))` with WHITE fill\n   - Call `apply_redactions()` to REMOVE text from PDF structure\n   - Then `insert_text()` to add masked value (e.g., \"****5678\")\n   - Original text is completely removed, not just covered\n\n## Overview\n\n**USE PYTHON WITH PyMuPDF (fitz)** - it is pre-installed and produces the best results.\n\nPyMuPDF preserves the text layer properly, making text extractable after editing.\nJavaScript libraries like pdf-lib may create text that tools like pypdf cannot extract.\n\n```bash\n# PyMuPDF is already installed - just use it\npython3 -c \"import fitz; print('PyMuPDF ready')\"\n```\n\n## Reading PDF Content\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Extract all text to understand the document\ntext = page.get_text()\nprint(text)\n```\n\n## Finding Text Positions\n\n```python\n# search_for() returns list of rectangles where text is found\nrects = page.search_for(\"Label Text\")\nif rects:\n    rect = rects[0]\n    # rect.x0, rect.y0 = top-left corner\n    # rect.x1, rect.y1 = bottom-right corner\n    print(f\"Found at: ({rect.x0}, {rect.y0}) to ({rect.x1}, {rect.y1})\")\n```\n\n## Inserting Text\n\n```python\n# Insert text at a specific position\npage.insert_text(\n    (x_position, y_position),  # coordinates\n    \"text to insert\",\n    fontsize=11,\n    color=(0, 0, 0)  # black\n)\n\ndoc.save(\"output.pdf\")\n```\n\n## Common Pattern: Fill Empty Form Fields\n\nWhen a form field is empty (no existing value), insert text next to the label:\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Find label, insert value to the right of it\nlabel_rect = page.search_for(\"FIELD LABEL:\")[0]\npage.insert_text((label_rect.x1 + 5, label_rect.y1), \"value\", fontsize=11)\n\n# For today's date\nfrom datetime import datetime\ndate_rect = page.search_for(\"Date\")[0]\ntoday = datetime.now().strftime(\"%Y\u002F%m\u002F%d\")\npage.insert_text((date_rect.x1 + 5, date_rect.y1), today, fontsize=11)\n\n# For signatures - insert the person's name\nsig_rect = page.search_for(\"signature\")[0]\npage.insert_text((sig_rect.x1 + 5, sig_rect.y1), \"Full Name\", fontsize=12)\n\ndoc.save(\"output.pdf\")\n```\n\n## Replacing Existing Text (CRITICAL - MUST FOLLOW)\n\nWhen you need to replace text that's already in the PDF with new\u002Fcorrect values:\n\n1. First extract the PDF text to see what's currently there\n2. Compare with the correct values from your input source\n3. For any value that needs to change: **COVER with white rectangle, then insert new text AT THE SAME POSITION**\n\n### WRONG vs RIGHT approaches:\n\n**WRONG - DO NOT DO THIS:**\n```python\n# WRONG: Adding text next to old value\npage.insert_text((old_rect.x1 + 10, old_rect.y1), new_value)  # NO!\n\n# WRONG: Using strikethrough\npage.draw_line(start, end, color=(0,0,0))  # NO!\n\n# WRONG: Rasterizing to image\npix = page.get_pixmap()  # NO! Destroys text layer\n```\n\n**RIGHT - DO THIS:**\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# First, extract text to see what's in the PDF\ncurrent_text = page.get_text()\nprint(current_text)  # Examine what values exist\n\n# To replace a value you found that needs changing:\nold_value = \"...\"  # The value you found in the PDF that's wrong\nnew_value = \"...\"  # The correct value from your input source\n\nrects = page.search_for(old_value)\nif rects:\n    rect = rects[0]\n\n    # STEP 1: Draw WHITE rectangle to COMPLETELY COVER old text\n    page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)\n\n    # STEP 2: Insert new text at the SAME position (not offset!)\n    page.insert_text((rect.x0, rect.y1), new_value, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n**Key points:**\n- `draw_rect(rect, fill=(1,1,1), width=0)` draws a WHITE filled rectangle\n- `(1, 1, 1)` is white in RGB (0-1 scale)\n- Insert new text at `rect.x0` (same X position), NOT `rect.x1 + offset`\n- The old text becomes invisible under the white rectangle\n- The new text appears in the same location\n- Text layer is preserved (text remains extractable)\n\n## TRUE REDACTION for Sensitive Data (IMPORTANT!)\n\nFor sensitive data like student IDs, you must use TRUE REDACTION that removes the original text from the PDF structure. A white rectangle only VISUALLY covers text - tools like pypdf can still extract the hidden text!\n\n**CRITICAL DISTINCTION:**\n- `draw_rect()` = Visual cover only (text still extractable by machines)\n- `add_redact_annot()` + `apply_redactions()` = TRUE redaction (text removed from PDF)\n\n**Example:** \"A12345678\" should become \"****5678\" (show only last 4 digits)\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# 1. First find what value is in the PDF by reading the text\npdf_text = page.get_text()\n# Find the ID in the text (e.g., \"A88888888\")\n\noriginal_in_pdf = \"A88888888\"  # Value you found in the PDF\n# Extract just the digits for masking\ndigits = ''.join(c for c in original_in_pdf if c.isdigit())\nmasked = \"****\" + digits[-4:]  # Result: \"****8888\"\n\nrects = page.search_for(original_in_pdf)\nif rects:\n    rect = rects[0]\n\n    # STEP 1: Create TIGHT bounding box to avoid covering nearby labels\n    tight_rect = fitz.Rect(rect.x0, rect.y0 + 8, rect.x1, rect.y1 - 2)\n\n    # STEP 2: Add redaction annotation with WHITE fill (not black!)\n    page.add_redact_annot(tight_rect, fill=(1, 1, 1))  # WHITE fill\n\n    # STEP 2: Apply redactions - this REMOVES the text from PDF structure\n    page.apply_redactions()\n\n    # STEP 3: Insert masked value at the same position\n    page.insert_text((rect.x0, rect.y1), masked, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n**Why this works:**\n- `add_redact_annot(rect, fill=(1, 1, 1))` - marks area with WHITE rectangle\n- `apply_redactions()` - REMOVES the underlying text from PDF structure\n- `insert_text()` - adds the masked value\n\n**WRONG approaches:**\n```python\n# WRONG: Black box redaction (ugly and suspicious)\npage.add_redact_annot(rect, fill=(0, 0, 0))  # NO! Use white fill\n\n# WRONG: Only using draw_rect (text still extractable!)\npage.draw_rect(rect, fill=(1, 1, 1))  # NO! This only covers visually\npage.insert_text(...)  # Original text is still in PDF structure!\n\n# WRONG: Adding masked value NEXT TO original\npage.insert_text((rect.x1 + 10, rect.y1), masked)  # NO!\n```\n\n\n\n## Workflow: Compare and Update\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Step 1: Read PDF to see current content\npdf_text = page.get_text()\nprint(pdf_text)\n\n# Step 2: Read your input source to get correct values\n# (parse input file, extract the values you need)\n\n# Step 3: For each value that differs, replace it\n# Build a dict of {old_value_in_pdf: correct_value}\nreplacements = {}  # Populate by comparing PDF content with correct values\n\nfor old_val, new_val in replacements.items():\n    if old_val == new_val:\n        continue  # Skip if already correct\n    rects = page.search_for(old_val)\n    if rects:\n        rect = rects[0]\n        # Cover old text with white rectangle\n        page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)\n        # Insert new text\n        page.insert_text((rect.x0, rect.y1), new_val, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n## Important Guidelines\n\n1. **COVER then REPLACE** - Use white rectangle to cover old text, insert new text at SAME position\n2. **Never use strikethrough** - No lines through text, no crossing out\n3. **Never rasterize** - No converting PDF to images, no get_pixmap() workflows\n4. **Never add text NEXT TO old values** - Replace AT the same position\n5. **Never use add_redact_annot() with black** - It creates black boxes\n6. **Preserve all labels** - Form labels should remain visible\n7. **Preserve text layer** - Text must remain extractable after editing\n8. **Match font size** - Typically 10-12pt for forms\n\n## Alternative: JavaScript with pdf-lib (NOT RECOMMENDED)\n\n**WARNING:** pdf-lib may create text that cannot be extracted by pypdf.\nUse Python with PyMuPDF instead whenever possible.\n\nIf you must use Node.js\u002FJavaScript, use pdf-lib with the same approach:\n\n```javascript\nconst { PDFDocument, rgb } = require('pdf-lib');\nconst fs = require('fs');\n\nasync function editPdf() {\n  const pdfBytes = fs.readFileSync('input.pdf');\n  const pdfDoc = await PDFDocument.load(pdfBytes);\n  const page = pdfDoc.getPages()[0];\n  const { height } = page.getSize();\n\n  \u002F\u002F To replace text at known coordinates:\n  \u002F\u002F STEP 1: Draw WHITE rectangle to cover old text\n  page.drawRectangle({\n    x: oldTextX,\n    y: oldTextY,\n    width: oldTextWidth,\n    height: oldTextHeight,\n    color: rgb(1, 1, 1),  \u002F\u002F WHITE\n  });\n\n  \u002F\u002F STEP 2: Draw new text at the SAME position\n  page.drawText('New Value', {\n    x: oldTextX,  \u002F\u002F SAME X position, not offset!\n    y: oldTextY,\n    size: 11,\n    color: rgb(0, 0, 0),\n  });\n\n  fs.writeFileSync('output.pdf', await pdfDoc.save());\n}\n```\n\n**WRONG with pdf-lib:**\n```javascript\n\u002F\u002F WRONG: Adding text to the right of old value\npage.drawText('New Value', {\n  x: oldTextX + oldTextWidth + 10,  \u002F\u002F NO! Don't offset\n  ...\n});\n\n\u002F\u002F WRONG: Drawing strikethrough line\npage.drawLine({\n  start: { x: x1, y: y },\n  end: { x: x2, y: y },\n  color: rgb(0, 0, 0),  \u002F\u002F NO strikethrough!\n});\n```\n\n","# PDF Editing Skill\n\n## CRITICAL RULES - READ FIRST\n\n**NEVER DO THESE:**\n- NEVER use strikethrough lines to cross out text\n- NEVER rasterize or flatten the PDF to images\n- NEVER convert PDF pages to PNG\u002FJPG and draw on them\n- NEVER use pdf-to-image-to-pdf workflows\n- NEVER use add_redact_annot() with BLACK fill (use WHITE fill instead)\n- NEVER add text NEXT TO old values - REPLACE them at the SAME position\n\n**TWO APPROACHES - CHOOSE THE RIGHT ONE:**\n\n1. **For REPLACING text** (e.g., updating name, email, DOB):\n   - Use `draw_rect()` with white fill to cover old text\n   - Use `insert_text()` at the SAME position\n   - Text layer is preserved\n\n2. **For TRUE REDACTION of sensitive data** (e.g., student ID):\n   - Use `add_redact_annot(rect, fill=(1,1,1))` with WHITE fill\n   - Call `apply_redactions()` to REMOVE text from PDF structure\n   - Then `insert_text()` to add masked value (e.g., \"****5678\")\n   - Original text is completely removed, not just covered\n\n## Overview\n\n**USE PYTHON WITH PyMuPDF (fitz)** - it is pre-installed and produces the best results.\n\nPyMuPDF preserves the text layer properly, making text extractable after editing.\nJavaScript libraries like pdf-lib may create text that tools like pypdf cannot extract.\n\n```bash\n# PyMuPDF is already installed - just use it\npython3 -c \"import fitz; print('PyMuPDF ready')\"\n```\n\n## Reading PDF Content\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Extract all text to understand the document\ntext = page.get_text()\nprint(text)\n```\n\n## Finding Text Positions\n\n```python\n# search_for() returns list of rectangles where text is found\nrects = page.search_for(\"Label Text\")\nif rects:\n    rect = rects[0]\n    # rect.x0, rect.y0 = top-left corner\n    # rect.x1, rect.y1 = bottom-right corner\n    print(f\"Found at: ({rect.x0}, {rect.y0}) to ({rect.x1}, {rect.y1})\")\n```\n\n## Inserting Text\n\n```python\n# Insert text at a specific position\npage.insert_text(\n    (x_position, y_position),  # coordinates\n    \"text to insert\",\n    fontsize=11,\n    color=(0, 0, 0)  # black\n)\n\ndoc.save(\"output.pdf\")\n```\n\n## Common Pattern: Fill Empty Form Fields\n\nWhen a form field is empty (no existing value), insert text next to the label:\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Find label, insert value to the right of it\nlabel_rect = page.search_for(\"FIELD LABEL:\")[0]\npage.insert_text((label_rect.x1 + 5, label_rect.y1), \"value\", fontsize=11)\n\n# For today's date\nfrom datetime import datetime\ndate_rect = page.search_for(\"Date\")[0]\ntoday = datetime.now().strftime(\"%Y\u002F%m\u002F%d\")\npage.insert_text((date_rect.x1 + 5, date_rect.y1), today, fontsize=11)\n\n# For signatures - insert the person's name\nsig_rect = page.search_for(\"signature\")[0]\npage.insert_text((sig_rect.x1 + 5, sig_rect.y1), \"Full Name\", fontsize=12)\n\ndoc.save(\"output.pdf\")\n```\n\n## Replacing Existing Text (CRITICAL - MUST FOLLOW)\n\nWhen you need to replace text that's already in the PDF with new\u002Fcorrect values:\n\n1. First extract the PDF text to see what's currently there\n2. Compare with the correct values from your input source\n3. For any value that needs to change: **COVER with white rectangle, then insert new text AT THE SAME POSITION**\n\n### WRONG vs RIGHT approaches:\n\n**WRONG - DO NOT DO THIS:**\n```python\n# WRONG: Adding text next to old value\npage.insert_text((old_rect.x1 + 10, old_rect.y1), new_value)  # NO!\n\n# WRONG: Using strikethrough\npage.draw_line(start, end, color=(0,0,0))  # NO!\n\n# WRONG: Rasterizing to image\npix = page.get_pixmap()  # NO! Destroys text layer\n```\n\n**RIGHT - DO THIS:**\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# First, extract text to see what's in the PDF\ncurrent_text = page.get_text()\nprint(current_text)  # Examine what values exist\n\n# To replace a value you found that needs changing:\nold_value = \"...\"  # The value you found in the PDF that's wrong\nnew_value = \"...\"  # The correct value from your input source\n\nrects = page.search_for(old_value)\nif rects:\n    rect = rects[0]\n\n    # STEP 1: Draw WHITE rectangle to COMPLETELY COVER old text\n    page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)\n\n    # STEP 2: Insert new text at the SAME position (not offset!)\n    page.insert_text((rect.x0, rect.y1), new_value, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n**Key points:**\n- `draw_rect(rect, fill=(1,1,1), width=0)` draws a WHITE filled rectangle\n- `(1, 1, 1)` is white in RGB (0-1 scale)\n- Insert new text at `rect.x0` (same X position), NOT `rect.x1 + offset`\n- The old text becomes invisible under the white rectangle\n- The new text appears in the same location\n- Text layer is preserved (text remains extractable)\n\n## TRUE REDACTION for Sensitive Data (IMPORTANT!)\n\nFor sensitive data like student IDs, you must use TRUE REDACTION that removes the original text from the PDF structure. A white rectangle only VISUALLY covers text - tools like pypdf can still extract the hidden text!\n\n**CRITICAL DISTINCTION:**\n- `draw_rect()` = Visual cover only (text still extractable by machines)\n- `add_redact_annot()` + `apply_redactions()` = TRUE redaction (text removed from PDF)\n\n**Example:** \"A12345678\" should become \"****5678\" (show only last 4 digits)\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# 1. First find what value is in the PDF by reading the text\npdf_text = page.get_text()\n# Find the ID in the text (e.g., \"A88888888\")\n\noriginal_in_pdf = \"A88888888\"  # Value you found in the PDF\n# Extract just the digits for masking\ndigits = ''.join(c for c in original_in_pdf if c.isdigit())\nmasked = \"****\" + digits[-4:]  # Result: \"****8888\"\n\nrects = page.search_for(original_in_pdf)\nif rects:\n    rect = rects[0]\n\n    # STEP 1: Create TIGHT bounding box to avoid covering nearby labels\n    tight_rect = fitz.Rect(rect.x0, rect.y0 + 8, rect.x1, rect.y1 - 2)\n\n    # STEP 2: Add redaction annotation with WHITE fill (not black!)\n    page.add_redact_annot(tight_rect, fill=(1, 1, 1))  # WHITE fill\n\n    # STEP 2: Apply redactions - this REMOVES the text from PDF structure\n    page.apply_redactions()\n\n    # STEP 3: Insert masked value at the same position\n    page.insert_text((rect.x0, rect.y1), masked, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n**Why this works:**\n- `add_redact_annot(rect, fill=(1, 1, 1))` - marks area with WHITE rectangle\n- `apply_redactions()` - REMOVES the underlying text from PDF structure\n- `insert_text()` - adds the masked value\n\n**WRONG approaches:**\n```python\n# WRONG: Black box redaction (ugly and suspicious)\npage.add_redact_annot(rect, fill=(0, 0, 0))  # NO! Use white fill\n\n# WRONG: Only using draw_rect (text still extractable!)\npage.draw_rect(rect, fill=(1, 1, 1))  # NO! This only covers visually\npage.insert_text(...)  # Original text is still in PDF structure!\n\n# WRONG: Adding masked value NEXT TO original\npage.insert_text((rect.x1 + 10, rect.y1), masked)  # NO!\n```\n\n\n\n## Workflow: Compare and Update\n\n```python\nimport fitz\n\ndoc = fitz.open(\"input.pdf\")\npage = doc[0]\n\n# Step 1: Read PDF to see current content\npdf_text = page.get_text()\nprint(pdf_text)\n\n# Step 2: Read your input source to get correct values\n# (parse input file, extract the values you need)\n\n# Step 3: For each value that differs, replace it\n# Build a dict of {old_value_in_pdf: correct_value}\nreplacements = {}  # Populate by comparing PDF content with correct values\n\nfor old_val, new_val in replacements.items():\n    if old_val == new_val:\n        continue  # Skip if already correct\n    rects = page.search_for(old_val)\n    if rects:\n        rect = rects[0]\n        # Cover old text with white rectangle\n        page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0)\n        # Insert new text\n        page.insert_text((rect.x0, rect.y1), new_val, fontsize=11, color=(0, 0, 0))\n\ndoc.save(\"output.pdf\")\n```\n\n## Important Guidelines\n\n1. **COVER then REPLACE** - Use white rectangle to cover old text, insert new text at SAME position\n2. **Never use strikethrough** - No lines through text, no crossing out\n3. **Never rasterize** - No converting PDF to images, no get_pixmap() workflows\n4. **Never add text NEXT TO old values** - Replace AT the same position\n5. **Never use add_redact_annot() with black** - It creates black boxes\n6. **Preserve all labels** - Form labels should remain visible\n7. **Preserve text layer** - Text must remain extractable after editing\n8. **Match font size** - Typically 10-12pt for forms\n\n## Alternative: JavaScript with pdf-lib (NOT RECOMMENDED)\n\n**WARNING:** pdf-lib may create text that cannot be extracted by pypdf.\nUse Python with PyMuPDF instead whenever possible.\n\nIf you must use Node.js\u002FJavaScript, use pdf-lib with the same approach:\n\n```javascript\nconst { PDFDocument, rgb } = require('pdf-lib');\nconst fs = require('fs');\n\nasync function editPdf() {\n  const pdfBytes = fs.readFileSync('input.pdf');\n  const pdfDoc = await PDFDocument.load(pdfBytes);\n  const page = pdfDoc.getPages()[0];\n  const { height } = page.getSize();\n\n  \u002F\u002F To replace text at known coordinates:\n  \u002F\u002F STEP 1: Draw WHITE rectangle to cover old text\n  page.drawRectangle({\n    x: oldTextX,\n    y: oldTextY,\n    width: oldTextWidth,\n    height: oldTextHeight,\n    color: rgb(1, 1, 1),  \u002F\u002F WHITE\n  });\n\n  \u002F\u002F STEP 2: Draw new text at the SAME position\n  page.drawText('New Value', {\n    x: oldTextX,  \u002F\u002F SAME X position, not offset!\n    y: oldTextY,\n    size: 11,\n    color: rgb(0, 0, 0),\n  });\n\n  fs.writeFileSync('output.pdf', await pdfDoc.save());\n}\n```\n\n**WRONG with pdf-lib:**\n```javascript\n\u002F\u002F WRONG: Adding text to the right of old value\npage.drawText('New Value', {\n  x: oldTextX + oldTextWidth + 10,  \u002F\u002F NO! Don't offset\n  ...\n});\n\n\u002F\u002F WRONG: Drawing strikethrough line\npage.drawLine({\n  start: { x: x1, y: y },\n  end: { x: x2, y: y },\n  color: rgb(0, 0, 0),  \u002F\u002F NO strikethrough!\n});\n```\n\n",{"name":5,"description":7},"main","7eeb258ada72b7f58119b0e7a2c00ea89d9beedd","tasks\u002Fedit-pdf\u002Fenvironment\u002Fskills\u002Fpdf-editing","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench","https:\u002F\u002Fgithub.com\u002FWaaaalnut","student of Zhejiang University","2026-04-21 23:16:23",{"source":8,"skillName":5,"commands":46,"prompt":56,"zipAvailable":57},[47,50,53],{"tool":48,"command":49},"npx","npx skills add Waaaalnut\u002Fskillsbench --skill pdf-editing",{"tool":51,"command":52},"bunx","bunx skills add Waaaalnut\u002Fskillsbench --skill pdf-editing",{"tool":54,"command":55},"pnpm","pnpm dlx skills add Waaaalnut\u002Fskillsbench --skill pdf-editing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fedit-pdf\u002Fenvironment\u002Fskills\u002Fpdf-editing\u002FSKILL.md",true,[],[60,74,89,115,137,155],{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":70,"publishedAt":33},"01M6XEWMJ6VY3ZC53MPDCPG3GH","data-reconciliation","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fxlsx-recover-data\u002Fenvironment\u002Fskills\u002Fdata-reconciliation\u002FSKILL.md",[65],{"categoryNo":66,"categoryName":67,"taxonomyType":68,"taxonomyTypeDesc":69},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[71,72,73],"data reconciliation","reconciliation","data",{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":33},"01M6XEW8MNPMF1KRXV6ATGFRQF","speech-to-text","Transcribe video to timestamped text using Whisper tiny model (pre-installed).","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-tutorial-indexer\u002Fenvironment\u002Fskills\u002Fspeech-to-text\u002FSKILL.md",[],[81,82,83,84,85,86,87,88],"speech","text","speech to text","transcribe video","whisper","model","Video","transcribe",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":98,"publishedAt":33},"01M6XEW3FQZP0T1RKQQJFXVY9T","video-processor","Process videos by removing segments and concatenating remaining parts. Use when you need to remove detected pauses\u002Fopenings from videos, create highlight reels, or batch process segment removals using ffmpeg filter_complex.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fvideo-processor\u002FSKILL.md",[95],{"categoryNo":96,"categoryName":97,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000016","图像与多媒体",[87,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114],"processor","video processor","batch","create","reels","when","parts","segments","process","videos","you","segment","filter","ffmpeg","use","remove",{"skillNo":116,"name":117,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":122,"publishedAt":33},"01M6XEVXJ5KZ0NTY3XZKRY4AS1","silence-detector","Detect initial silence segments in audio\u002Fvideo using energy-based analysis. Use when you need to find low-energy periods at the start of recordings (title slides, setup time, pre-roll silence).","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fsilence-detector\u002FSKILL.md",[121],{"categoryNo":96,"categoryName":97,"taxonomyType":20,"taxonomyTypeDesc":21},[123,124,125,126,104,109,127,128,129,130,131,106,132,133,87,134,135,136,113],"detector","silence detector","title","slides","time","energy","at","recordings","detect","find","Audio","analysis","start","setup",{"skillNo":138,"name":139,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":143,"publishedAt":33},"01M6XEVT3NGRN1FT7JPMAPXS6M","segment-combiner","Combine multiple segment detection results into a unified list. Use when you need to merge segments from different detectors, prepare removal lists for video processing, or consolidate detection outputs.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fsegment-combiner\u002FSKILL.md",[],[110,144,87,145,146,147,148,32,104,149,150,106,109,151,152,113,153,154],"segment combiner","detection","prepare","merge","video processing","outputs","combine","processing","consolidate","results","list",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":164,"publishedAt":33},"01M6XEVMPVASGS2KX86QK4FMT7","report-generator","Generate compression reports for video processing. Use when you need to create structured JSON reports with duration statistics, compression ratios, and segment details after video processing.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Freport-generator\u002FSKILL.md",[161],{"categoryNo":162,"categoryName":163,"taxonomyType":68,"taxonomyTypeDesc":69},"01JBSEEDCAT000000000000034","行政与办公人员",[165,166,167,110,104,32,109,30,151,168,169,170,113,148,171,87,172,173,102],"generator","report generator","report","statistics","structured","reports","json","compression","generate"]