{"id":24407509,"url":"https://github.com/esdmr/badpdf","last_synced_at":"2026-02-18T02:02:25.552Z","repository":{"id":272939308,"uuid":"918237723","full_name":"esdmr/badpdf","owner":"esdmr","description":"🍎▶📄 [東方] Bad Apple in PDF","archived":false,"fork":false,"pushed_at":"2025-02-12T14:51:52.000Z","size":177,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-25T13:34:10.620Z","etag":null,"topics":["bad-apple","pdf","touhou"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esdmr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-17T14:23:51.000Z","updated_at":"2025-06-23T10:35:38.000Z","dependencies_parsed_at":"2025-01-17T15:39:58.589Z","dependency_job_id":"56860aa1-04a2-412d-a542-9cfdad03d8ec","html_url":"https://github.com/esdmr/badpdf","commit_stats":null,"previous_names":["esdmr/badpdf"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/esdmr/badpdf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esdmr%2Fbadpdf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esdmr%2Fbadpdf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esdmr%2Fbadpdf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esdmr%2Fbadpdf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esdmr","download_url":"https://codeload.github.com/esdmr/badpdf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esdmr%2Fbadpdf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29566366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T00:47:08.760Z","status":"online","status_checked_at":"2026-02-18T02:00:09.468Z","response_time":162,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bad-apple","pdf","touhou"],"created_at":"2025-01-20T05:11:33.893Z","updated_at":"2026-02-18T02:02:25.522Z","avatar_url":"https://github.com/esdmr.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bad Apple in PDF\n\nAn experiment to play [“Bad Apple!!”][bad-apple] in a JavaScript-enabled PDF\nviewer. It is tested in Chromium (PDFium) and Firefox (PDF.js). The initial page\nload might take a few seconds.\n\n## Download\n\n|                                            Name                                             | Description                                           |\n| :-----------------------------------------------------------------------------------------: | :---------------------------------------------------- |\n|    [`bad-hc-px.pdf`](https://github.com/esdmr/badpdf/releases/download/v2/bad-hc-px.pdf)    | slower, works on both Firefox and Chromium            |\n|    [`bad-hc-rw.pdf`](https://github.com/esdmr/badpdf/releases/download/v2/bad-hc-rw.pdf)    | faster, best viewed in Chromium                       |\n| [`bad-orig-res.pdf`](https://github.com/esdmr/badpdf/releases/download/v2/bad-orig-res.pdf) | original resolution, best viewed in Chromium          |\n| [`bad-half-res.pdf`](https://github.com/esdmr/badpdf/releases/download/v2/bad-half-res.pdf) | half the original resolution, best viewed in Chromium |\n\nCheck the [latest release](https://github.com/esdmr/badpdf/releases/latest) for more.\n\n## Requirement\n\n- [UV][uv]\n- [Node.JS][nodejs]\n- [FFmpeg][ffmpeg]\n- [jq][jq]\n- [Git][git]\n- [GNU Make][make]\n- POSIX-compatible `/bin/sh`\n\n## Building\n\n```sh\ngit clone https://github.com/esdmr/badpdf.git --recurse-submodules\ncd badpdf\nmake\n\n# Or, to run a dev server\nmake dev\n```\n\n## Development\n\nThis involved down scaling the original video and encoding it using\nRun-Length Encoding (plus some optional, lossy compression). Then a script\nencoded the resulting frame data into base64 and embedded it in a PDF template,\nalong with the JavaScript code to play it.\n\nThe original video is also available at the\n[Felixoofed/badapple-frames][badapple-frames] repository, so I added it as a\nsubmodule at `frames/src`.\n\nThe bash script over at `frames/create-frames.sh` downscales the video and\nlowers the video frame rate using [FFmpeg][ffmpeg] commandline interface. The output goes\ninto the `frames/out` directory.\n\nThe python script over at `frames/process_frames.py` loads each frame and converts\nthem into black and white. Then, it encodes the run-length of the values.\nFinally, it does two lossy compressions on the RLE’d stream.\n\nOptionally, this script reorders the frame data according to a [Generalized\nHilbert Curve][gilbert]. This improves the compression rate of RLE, compared to\nthe linear scan curve.\n\nThe first is the “RLE Ridge” compression. Its goal is to reduce very short\nsegments, by intentionally miscoloring that segment, thereby combining three\nsegments together. This will diminish small details, so it is best to keep the\nthreshold small.\n\n```\nrle:   0:355 1:001 0:050 (3 segments, 4 bytes)\nridge: 0:406             (1 segment,  2 bytes)\n```\nThe second is the “RLE Plain” compression. It is very similar to the Ridge\ncompression, but it only compresses odd number of short segments into one.\nUnlike the Ridge compression, it expands small details, so it is best to keep\nthe threshold small. Since Ridge compression already deals with very small\nsegments, the threshold should be greater than that of Ridge’s, otherwise Plain\nwould not do anything.\n\n```\nrle:   0:355 1:003 0:002 1:004 0:257 (5 segments, 7 bytes)\nplain: 0:355 1:009             0:257 (3 segments, 5 bytes)\n```\n\nThe final frame data goes to the `frames/out/frames.bin` file. If you run the\npreview server, you can see how it will look like outside a PDF sandbox.\n\nThe python script over at `generate.py` embeds the font and the JavaScript, and\ngenerates the AcroForm widgets for JavaScript to use for displaying the frame\ndata. It uses [pikepdf][pikepdf] to generate the PDF, which automatically\ncompresses the PDF stream data.\n\nThe generated PDF may use either pixels (like PDFTris) or rows (like DoomPDF) to\ndisplay the data. The rows display is much more performant and is able to\nsupport higher resolution without overwhelming the PDF sandbox and renderer. To\nimprove the graphical quality of the row display, the generator script embeds a\nType1 font `bw.pfb` to use for the text fields. (Unfortunately, PDF.js does not\nsupport fonts in text fields yet.)\n\n## Related Work\n\n- [PDFTris][pdftris]\n- [DoomPDF][doompdf]\n\n[bad-apple]: https://www.youtube.com/watch?v=i41KoE0iMYU\n[badapple-frames]: https://github.com/Felixoofed/badapple-frames\n[pdftris]: https://github.com/ThomasRinsma/pdftris\n[doompdf]: https://github.com/ading2210/doompdf\n[uv]: https://docs.astral.sh/uv/\n[ffmpeg]: https://www.ffmpeg.org/\n[make]: https://www.gnu.org/software/make/\n[git]: https://git-scm.com/\n[gilbert]: https://github.com/jakubcerveny/gilbert\n[nodejs]: https://nodejs.org/\n[jq]: https://jqlang.github.io/jq/\n[pikepdf]: https://pikepdf.readthedocs.io/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesdmr%2Fbadpdf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesdmr%2Fbadpdf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesdmr%2Fbadpdf/lists"}