Skip to content

cdp: implement a fake Page.printToPDF#2197

Merged
krichprollsch merged 1 commit into
mainfrom
fake-printopdf
Apr 20, 2026
Merged

cdp: implement a fake Page.printToPDF#2197
krichprollsch merged 1 commit into
mainfrom
fake-printopdf

Conversation

@krichprollsch

@krichprollsch krichprollsch commented Apr 20, 2026

Copy link
Copy Markdown
Member

Similar to Page.captureScreenshot
This is useful for tools integration calling this method, returning a fake result instead of an error.

Relates with NousResearch/hermes-agent#7144 (comment)

@krichprollsch krichprollsch self-assigned this Apr 20, 2026
@karlseguin

Copy link
Copy Markdown
Collaborator

Look at at what Claude created (6.5KB):

screenshot-mini.pdf

Via this script:

#!/usr/bin/env ruby
# Build a small PDF of the Lightpanda "no screenshot" screen using only vector
# paths for the logo and PDF's built-in Helvetica (no font embedding).

PATHS = [
  ['M59.8175 53.1278C63.226 55.6474 66.8494 59.8264 68.5383 62.2538C68.9682 62.8684 68.4462 63.6673 67.7092 63.5444C61.6906 62.3768 41.6696 58.1364 24.6885 49.8092C24.32 49.6248 24.1358 49.2254 24.2586 48.8567C24.6271 47.5661 25.9168 44.9235 30.093 44.7392C30.7378 44.7084 31.1063 44.0017 30.7685 43.4793C29.878 42.0966 28.5269 39.9457 28.0049 38.2557C27.9435 38.0406 27.7899 37.8869 27.575 37.7947C26.7766 37.3953 24.4122 36.3506 19.9596 34.9985C14.3095 33.2778 12.1293 33.3393 11.0546 31.5878C9.97982 29.8363 9.94911 27.9927 12.4978 26.5485C15.0465 25.1043 25.9782 22.7075 29.7245 22.8612C29.7245 22.8612 30.9221 22.9226 30.8607 22.2159C30.7992 21.5092 30.2465 20.3415 30.2158 18.4364C30.1851 16.3162 32.5803 14.9642 34.7298 14.2882C36.7871 13.6736 39.3051 14.9027 41.1475 15.8246C41.3318 15.9168 41.5774 15.9475 41.7617 15.8553C42.7136 15.5173 45.815 14.5955 49.4999 15.1486C50.0526 15.2407 51.8029 12.3216 53.9831 12.5367C56.4704 12.7825 57.668 12.9055 58.5892 14.9027C59.3876 16.6542 59.2033 17.4839 58.7734 18.5286C58.6506 18.8051 58.712 19.1431 58.8963 19.3582C59.6332 20.2801 61.5371 22.8304 62.8882 26.3026C64.5464 30.5123 63.7787 33.708 63.0417 36.3506C62.9803 36.5964 63.011 36.8422 63.1645 37.0573C64.4235 38.7166 70.1658 46.5214 71.3633 50.9154C72.7145 55.8625 73.39 57.614 70.6264 58.0749C67.8627 58.5051 66.9108 59.7956 63.748 56.0162C60.9229 52.6054 57.1152 50.9154 57.8522 51.407L59.5718 53.0049C59.6025 52.9741 59.7868 53.0971 59.8175 53.1278Z', '000000'],
  ['M44.4024 16.6852C44.4024 16.6852 33.0407 19.5428 31.9353 26.6716C30.4613 36.1664 32.3652 42.0354 41.4238 44.8009C51.8028 47.9965 60.5236 40.7141 61.5062 35.8284C62.4889 30.9428 62.4275 24.9202 57.3301 19.7272C52.2327 14.5343 44.4024 16.6852 44.4024 16.6852Z', 'ffffff'],
  ['M32.3346 49.2254C32.2118 49.1333 34.3613 53.896 35.0676 54.3262C35.7738 54.7257 38.5682 55.7089 39.3666 55.3709C39.3666 55.3709 37.8619 52.2675 39.6122 49.2562C39.6122 49.2562 36.9714 49.2254 36.1116 51.6529C36.1423 51.6222 33.8393 50.1165 32.3346 49.2254Z', 'ffffff'],
  ['M32.9181 50.6698C32.9181 50.6698 37.7699 54.5415 38.906 54.8181C38.906 54.8181 39.0903 55.617 38.9367 53.1895C38.7832 50.762 39.7658 48.1809 40.902 47.8429C42.0382 47.5049 36.9101 47.9044 36.603 49.9938C36.2959 52.0833 35.4975 49.8709 34.5763 48.7033C33.6858 47.5049 32.8567 45.9071 32.8874 47.2591C32.9181 48.6111 31.6899 49.1949 32.9181 50.6698Z', 'F6F5EC'],
  ['M50.6666 56.6614C51.5571 57.3988 58.6197 60.8403 60.2472 60.7481C61.9054 60.656 65.2832 61.1476 65.1296 60.7481C65.0068 60.3487 56.593 53.1277 53.6145 51.6221C50.9122 50.2701 50.206 49.6862 46.8282 49.1946C46.6439 49.1639 43.8189 48.7951 42.9591 49.4404C42.4678 49.8399 41.7001 51.2226 42.4064 51.7757C43.2662 52.4824 46.7975 53.4964 49.1312 55.7703C49.2233 55.8625 49.3462 55.9546 49.469 56.0161L50.6666 56.6614Z', '282727'],
  ['M61.6905 39.2389C61.6905 39.2389 58.6198 41.9736 59.4181 43.3871C60.2165 44.8006 64.0242 51.069 65.6517 53.3121C67.2792 55.5552 68.5382 56.4156 69.3058 56.139C70.0735 55.8625 68.6917 49.6555 66.1123 45.0157C63.5329 40.4065 62.3353 38.6243 61.6905 39.2389Z', '282727'],
  ['M14.1562 28.9145C14.2483 28.9453 19.9598 29.4369 22.2629 29.99C24.5659 30.5431 26.6233 31.3727 27.2374 29.3447C27.8516 27.3167 29.2948 25.9647 28.8342 25.5345C28.3736 25.1043 24.5659 25.1658 21.2495 25.8418C17.9332 26.5178 11.3925 27.9005 14.1562 28.9145Z', '282727'],
  ['M41.1782 33.2165C41.1782 33.2165 42.7749 34.6299 44.7402 34.4763C46.7054 34.3227 47.5652 33.0014 48.7628 31.3114C49.9604 29.6214 50.2982 27.9621 49.5919 26.3335C48.8856 24.7049 47.6574 23.568 44.3103 24.244C40.9632 24.92 39.7349 31.3114 41.1782 33.2165Z', '000000'],
  ['M57.4529 30.1128C57.4529 30.1128 56.4088 28.8222 55.4569 27.8697C54.505 26.9479 54.7199 25.1964 55.7333 24.3053C56.7466 23.4142 58.0977 23.2605 59.5409 26.1489C60.9842 29.0066 61.0456 32.1101 60.1244 34.261C60.1551 34.261 58.7118 31.3112 57.4529 30.1128Z', '000000'],
  ['M48.2101 36.1354C48.2101 36.1354 49.4077 37.7332 51.2194 38.2248C53.0005 38.7165 54.6586 38.0712 55.1192 37.5796C55.5491 37.1187 56.1326 36.1661 54.7508 35.4901C53.3689 34.8141 52.2021 33.9844 53.0926 33.1548C53.9831 32.3252 58.2207 32.3559 58.5891 33.0012C58.9576 33.6464 59.0497 34.1995 57.7908 35.2135C56.5318 36.2275 56.2247 36.8114 56.8388 37.5181C57.4837 38.2248 58.4356 37.7332 58.4356 37.7332C58.4356 37.7332 57.7908 38.8087 56.8081 38.6243C55.8562 38.4399 56.3782 37.979 55.8562 38.4399C55.3035 38.9008 54.4744 39.4539 53.3382 39.5154C52.2021 39.5768 50.3903 39.4539 48.9164 38.0712C47.4425 36.7192 47.7495 36.4426 47.8724 36.3197C47.9952 36.1968 48.0259 36.1661 48.2101 36.1354Z', '000000'],
  ['M0 64.7428C0 64.7428 15.8756 52.083 46.3063 67.9999C46.3063 67.9999 20.4816 43.848 0 64.7428Z', '6CC7E2'],
  ['M4.69824 59.2118C4.69824 59.2118 15.6607 48.9488 32.089 57.1223C48.5173 65.2958 60.8308 75.651 82.5715 59.2118C82.5715 59.2118 64.0551 66.74 47.5654 57.2145C31.0757 47.689 17.1346 44.3704 4.69824 59.2118Z', '007EAA'],
  ['M47.6576 32.5403C48.4378 32.5403 49.0702 31.9075 49.0702 31.1268C49.0702 30.3462 48.4378 29.7134 47.6576 29.7134C46.8775 29.7134 46.2451 30.3462 46.2451 31.1268C46.2451 31.9075 46.8775 32.5403 47.6576 32.5403Z', 'ffffff'],
  ['M58.0113 30.8234C58.7908 30.7915 59.3945 30.0782 59.3598 29.2304C59.3251 28.3826 58.665 27.7212 57.8855 27.7531C57.106 27.7851 56.5023 28.4983 56.537 29.3462C56.5717 30.194 57.2318 30.8554 58.0113 30.8234Z', 'ffffff'],
  ['M38.5068 19.0513C38.5068 19.0513 33.6858 27.0711 35.0062 32.9401C35.4361 34.8145 36.5108 43.7869 53.3076 44.1864C53.3076 44.1864 48.3945 46.8904 41.4547 44.8009C34.5149 42.7115 32.2733 38.9627 31.6591 34.4457C31.045 29.9288 31.9969 24.3057 34.0236 22.462C36.0502 20.6184 36.5109 20.0346 38.5068 19.0513Z', 'EDEDED'],
]

def fmt(n)
  # compact number formatting
  s = format('%.4f', n).sub(/\.?0+$/, '')
  s = '0' if s == '-0' || s.empty?
  s
end

def hex_to_rgb(hex)
  hex = hex.downcase
  r = hex[0, 2].to_i(16) / 255.0
  g = hex[2, 2].to_i(16) / 255.0
  b = hex[4, 2].to_i(16) / 255.0
  [r, g, b]
end

def svg_path_to_pdf(d)
  tokens = d.scan(/[MLCZHVSmlczhvs]|-?\d+(?:\.\d+)?(?:[eE]-?\d+)?/)
  out = []
  i = 0
  cmd = nil
  while i < tokens.length
    t = tokens[i]
    if t =~ /[A-Za-z]/
      cmd = t
      i += 1
      next
    end
    case cmd
    when 'M'
      x, y = tokens[i].to_f, tokens[i + 1].to_f
      out << "#{fmt(x)} #{fmt(y)} m"
      i += 2
      cmd = 'L'
    when 'L'
      x, y = tokens[i].to_f, tokens[i + 1].to_f
      out << "#{fmt(x)} #{fmt(y)} l"
      i += 2
    when 'C'
      x1, y1, x2, y2, x, y = (0..5).map { |k| tokens[i + k].to_f }
      out << "#{fmt(x1)} #{fmt(y1)} #{fmt(x2)} #{fmt(y2)} #{fmt(x)} #{fmt(y)} c"
      i += 6
    when 'Z', 'z'
      out << 'h'
    else
      raise "unhandled cmd: #{cmd}"
    end
  end
  out.join(' ')
end

PAGE_W = 1920
PAGE_H = 1080

# Panda transform: SVG translate(775,200) scale(4.5), then SVG Y-flip into PDF.
# Combined: a=4.5, b=0, c=0, d=-4.5, e=775, f=PAGE_H-200
panda_cm = "4.5 0 0 -4.5 775 #{PAGE_H - 200} cm"

content_lines = []
content_lines << 'q'
content_lines << panda_cm
last_color = nil
PATHS.each do |d, color|
  if color != last_color
    r, g, b = hex_to_rgb(color)
    content_lines << "#{fmt(r)} #{fmt(g)} #{fmt(b)} rg"
    last_color = color
  end
  content_lines << svg_path_to_pdf(d)
  content_lines << 'f'
end
content_lines << 'Q'

# Text: dark grey (#222222), Helvetica 38, two lines
tr, tg, tb = hex_to_rgb('222222')
content_lines << 'BT'
content_lines << "/F1 38 Tf"
content_lines << "#{fmt(tr)} #{fmt(tg)} #{fmt(tb)} rg"
content_lines << "540 #{PAGE_H - 670} Td"
content_lines << '(No screenshot available,) Tj'
content_lines << '0 -48 Td'
content_lines << '(Lightpanda has no graphical rendering engine.) Tj'
content_lines << 'ET'

content = content_lines.join("\n")

# Build PDF
objs = []
objs << "<< /Type /Catalog /Pages 2 0 R >>"
objs << "<< /Type /Pages /Kids [3 0 R] /Count 1 >>"
objs << "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 #{PAGE_W} #{PAGE_H}] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>"
objs << "<< /Length #{content.bytesize} >>\nstream\n#{content}\nendstream"
objs << "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>"

pdf = String.new
pdf << "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n".b
offsets = []
objs.each_with_index do |body, idx|
  offsets << pdf.bytesize
  pdf << "#{idx + 1} 0 obj\n#{body}\nendobj\n"
end
xref_off = pdf.bytesize
pdf << "xref\n0 #{objs.length + 1}\n"
pdf << "0000000000 65535 f \n"
offsets.each { |o| pdf << format("%010d 00000 n \n", o) }
pdf << "trailer\n<< /Size #{objs.length + 1} /Root 1 0 R >>\nstartxref\n#{xref_off}\n%%EOF\n"

File.binwrite(ARGV[0] || '/tmp/screenshot-mini.pdf', pdf)
puts "wrote #{ARGV[0] || '/tmp/screenshot-mini.pdf'} (#{pdf.bytesize} bytes)"

@krichprollsch

Copy link
Copy Markdown
Member Author

awesome 👀
I tried to convert an HTML into a PDF to have something smaller and I get a 30KB one 🙈

Similar to Page.captureScreenshot
This is useful for tools integration calling this method, returning a
fake result instead of an error.
@krichprollsch krichprollsch merged commit 9df41fd into main Apr 20, 2026
12 checks passed
@krichprollsch krichprollsch deleted the fake-printopdf branch April 20, 2026 15:54
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants