#! /usr/bin/env python ############################################## # Extract each vscodeicons symbol to a # # separate PDF file. # # # # In fact, this script creates only a Ninja # # build file to perform the operation. # # # # Author: Scott Pakin # ############################################## import glob import os import re import subprocess import sys import textwrap from pathlib import Path def kpsewhich(fname, must_exist=True): 'Find a filename in the TeX tree.' proc = subprocess.run(['kpsewhich', fname], capture_output=True, check=must_exist, encoding='utf-8') if proc.returncode != 0: return None return proc.stdout.strip() def header_string(): 'Return a "generated file" header string.' full_name = os.path.abspath(sys.argv[0]) gen_line = 'This is a generated file. DO NOT EDIT.' edit_line = f'Edit {full_name} instead.' max_chars = max(len(gen_line), len(edit_line)) hash_line = '#' * (max_chars + 4) return '\n'.join([ hash_line, '# %-*.*s #' % (max_chars, max_chars, gen_line), '# %-*.*s #' % (max_chars, max_chars, edit_line), hash_line, '', ]) def count_pdf_pages(fname): 'Use pdfinfo to query the number of pages in a PDF file.' proc = subprocess.run(['pdfinfo', fname], capture_output=True, check=True, encoding='utf-8') for ln in proc.stdout.split('\n'): fields = ln.split() if len(fields) == 2 and fields[0] == 'Pages:': return int(fields[1]) raise RuntimeError(f'failed to count pages of {fname}') # Generate a Ninja file that extracts each page of # vscodeicons-default-all.pdf, each page of vscodeicons-filetype-all.pdf, # and each page of vscodeicons-foldertype-all.pdf into a separate file. with open('vscodeicons.ninja', 'w') as w: # Write the header string then stop if any of the source PDF files is # unavailable. w.write(header_string()) cat_to_fname = { 'default': kpsewhich('vscodeicons-default-all.pdf', must_exist=False), 'filetype': kpsewhich('vscodeicons-filetype-all.pdf', must_exist=False), 'foldertype': kpsewhich('vscodeicons-foldertype-all.pdf', must_exist=False), } if any(cat_to_fname.values()) is None: w.write(''' rule touch command = touch $out build fakevscodeicons.sty : touch build VSCODEICONS : phony fakevscodeicons.sty ''') sys.exit(0) # Define a rule that converts vscodeicons.sty to fakevscodeicons.sty. source_sty = kpsewhich('vscodeicons.sty') full_name = os.path.abspath(sys.argv[0]) w.write(rf''' rule fake-vscodeicons-sty command = $ echo '%% This is a generated file. DO NOT EDIT.' > $out ; $ echo '%% Edit {full_name} instead.' >> $out ; $ echo '' >> $out ; $''') w.write(r''' sed -e 's!\\includegraphics\[page=#1, ##1\]{vscodeicons-\([a-z]\+\)-all.pdf}!\\includegraphics[##1]{vscodeicons/\1-#1}!' $in >> $out description = Generate $out from vscodeicons.sty ''') # Invoke the preceding rule. w.write(f'build fakevscodeicons.sty : fake-vscodeicons-sty {source_sty}\n') # Define a rule that copies vscodeicons-*-all.pdf locally, cleaning the # PDF as it does so. At the time of this writing (1-Jan-2026), some of # the vscodeicons PDF files contain incorrect object counts. qpdf can # fix those. w.write(''' rule clean-pdf command = qpdf $in $out ; true description = Fixing $out ''') # Invoke the preceding rule. for cat, fname in cat_to_fname.items(): w.write(f'build vscodeicons/vscodeicons-{cat}-all.pdf : clean-pdf {fname}\n') # Define a rule that extracts a single page of vscodeicons-*-all.pdf # into a separate file. w.write(''' rule extract-pdf-page command = $ pdfseparate -f $page -l $page $in "vscodeicons/${category}-%d.pdf" description = Extracting $category, page $page from vscodeicons ''') # Invoke the preceding rule one for each page of vscodeicons-default.pdf. all_pdfs = [] for cat, fname in cat_to_fname.items(): w.write('\n') num_pages = count_pdf_pages(fname) w.write('# Extract %d %s icons\n' % (num_pages, cat)) local_fname = Path('vscodeicons') / (Path(fname).name) for i in range(1, num_pages + 1): pdf = f'vscodeicons/{cat}-{i}.pdf' all_pdfs.append(pdf) w.write(f'build {pdf} : extract-pdf-page {local_fname}\n') w.write(f' category = {cat}\n') w.write(f' page = {i}\n') w.write('\n') # Define a phony rule that depends on fakevscodeicons.sty and all of the # output files. w.write('build VSCODEICONS : phony $\n') phony = all_pdfs + ['fakevscodeicons.sty'] lines = textwrap.wrap(' '.join(phony), break_long_words=False, break_on_hyphens=False, initial_indent=' ', subsequent_indent=' ') w.write('%s\n' % ' $\n'.join(lines))