|
# /// script |
|
# requires-python = ">=3.12" |
|
# dependencies = [ |
|
# "pillow >= 9.2.0", |
|
# ] |
|
# /// |
|
|
|
""" |
|
Fetches author names from an arXiv paper and visually superimposes them in a centered stack. |
|
|
|
This script uses the arXiv API to retrieve metadata for a given paper, extracts the list of authors, |
|
and renders their names stacked on top of each other in a single image. |
|
|
|
Inspired by the SIGTBD 2023 paper "Every Author as First Author" by Erik and Martin Demaine, |
|
the visualization emphasizes author equality by overlapping names in a unified display. |
|
|
|
Usage: |
|
python script.py <arxiv_url_or_id> |
|
|
|
Args: |
|
sys.argv[1] (str): The arXiv paper URL or ID to fetch author data from. |
|
|
|
Returns: |
|
Displays a semi-transparent stack of author names in a pop-up image window. |
|
""" |
|
|
|
import argparse |
|
import urllib.request |
|
import xml.etree.ElementTree as ET |
|
|
|
from urllib.parse import urlparse |
|
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
|
def parse_args(): |
|
""" |
|
Parses command-line arguments for customizing the author stack visualization. |
|
|
|
Arguments: |
|
input (str): arXiv paper ID or URL to retrieve author metadata. |
|
--font (str): Optional path to a .ttf font file for rendering text. Defaults to 'arial.ttf'. |
|
--width (int): Optional width of the output image in pixels. Defaults to 600. |
|
--height (int): Optional height of the output image in pixels. Defaults to 200. |
|
|
|
Returns: |
|
argparse.Namespace: Parsed arguments object containing user inputs. |
|
""" |
|
parser = argparse.ArgumentParser(description="Stack arXiv author names on an image.") |
|
parser.add_argument("input", help="arXiv paper ID or URL") |
|
parser.add_argument("--font", default="arial.ttf", help="Path to a .ttf font file") |
|
parser.add_argument("--width", type=int, default=600, help="Width of the image") |
|
parser.add_argument("--height", type=int, default=200, help="Height of the image") |
|
return parser.parse_args() |
|
|
|
|
|
def fetch_arxiv_authors(arxiv_id): |
|
""" |
|
Queries the arXiv API for a paper and extracts the list of author names. |
|
For more information, see info.arxiv.org/help/api/basics.html |
|
|
|
Args: |
|
arxiv_id (str): The arXiv paper ID. |
|
|
|
Returns: |
|
list of str: Author names. |
|
""" |
|
url = 'http://export.arxiv.org/api/query?id_list=' + arxiv_id |
|
response = urllib.request.urlopen(url) |
|
data = response.read().decode('utf-8') |
|
ns = {'atom': 'http://www.w3.org/2005/Atom'} |
|
xmldoc = ET.ElementTree(ET.fromstring(data)) |
|
return [name.text for name in xmldoc.findall('.//atom:author/atom:name', ns)] |
|
|
|
|
|
def draw_name_stack(names, image_size=(600, 200), font_path=None): |
|
""" |
|
Draws a stack of names centered on a blank canvas. |
|
|
|
Args: |
|
names (list of str): List of names to superimpose on the image. |
|
image_size (tuple): Size of the image in pixels (width, height). |
|
font_path (str): Path to a .ttf font file. Defaults to None. |
|
|
|
Returns: |
|
PIL.Image: Image object with the names drawn on it. |
|
""" |
|
# Create a blank image with white background |
|
img = Image.new('RGBA', image_size, 'white') |
|
draw = ImageDraw.Draw(img) |
|
|
|
# Load font |
|
if font_path: |
|
font = ImageFont.truetype(font_path or "arial.ttf", size=40) |
|
else: |
|
font = None |
|
|
|
# Center coordinates |
|
x = image_size[0] // 2 |
|
y = image_size[1] // 2 |
|
|
|
for name in names: |
|
# Get text bounding box |
|
bbox = draw.textbbox((0, 0), name, font=font) |
|
text_width = bbox[2] - bbox[0] |
|
text_height = bbox[3] - bbox[1] |
|
|
|
# Center the text |
|
text_x = x - text_width // 2 |
|
text_y = y - text_height // 2 |
|
|
|
# Draw the name with semi-transparent black |
|
draw.text((text_x, text_y), name, fill=(0, 0, 0, 128), font=font) |
|
|
|
img.show() |
|
return img |
|
|
|
|
|
if __name__ == "__main__": |
|
args = parse_args() |
|
arxiv_id = urlparse(args.input).path.split("/")[-1] |
|
authors = fetch_arxiv_authors(arxiv_id) |
|
draw_name_stack(authors, image_size=(args.width, args.height), font_path=args.font) |