Knoxville, TN

Adding bookmarks to PDFs with .NET

September 12, 2024

Over the last year or so, I’ve been working up some of my TTRPG adventure notes into PDFs that I’ve released on DMs Guild, DriveThruRPG, and itch.io.

Homebrewery is a pretty nice entry-level approach to this (as I wrote in my previous post), but you’re at the mercy of your browser and your print-to-PDF driver. There are some obvious but hard-to-fix issues like file size (my best shot at this was reducing the size of images and running the result through FoxIt’s free PDF compressor), but what’s less obvious is a lot of features you just don’t get without going through professional PDF authoring software.

One of those features that you just can’t get from print-to-PDF is bookmarks. If you’ve ever used a PDF version of a book as a reference, you know you’re flipping around a lot, and Ctrl-F or guessing page numbers or mindlessly scrolling is annoying. The longer the document, the more you need that handy menu on the sidebar.

Turns out, it’s pretty easy to create a LINQpad script using the free .NET library PDFSharp. (LINQPad is extremely handy for writing up little .NET scripts like you’d do in scripting languages or languages with interactive shells, plus it lets you visualize complex objects pretty easily when you’re prototyping.)

I just create a script that I run every time I create a new version, hard-coding a table of contents that I apply using PDFSharp’s Outlines collection. I also clean up the title, which is generated automatically from the page that gets printed.

void Main()
{
	var FILENAME = Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), "Ryuutama - A Mysterious Tune.pdf");

	var BOOKMARKS = new TOCEntry[] {
		new TOCEntry() { Name = "Plot", Page = 1 },
		new TOCEntry() { Name = "GM Overview", Page = 1, Children = new TOCEntry[] {
			new TOCEntry() { Name = "Setting", Page = 1 },
			new TOCEntry() { Name = "Characters", Page = 1 },
			new TOCEntry() { Name = "Monsters", Page = 1 },
			new TOCEntry() { Name = "Plot Resolution", Page = 1 }
		}},
		new TOCEntry() { Name = "Scenes", Page = 2, Children = new TOCEntry[] {
			new TOCEntry() { Name = "Entering The Haile", Page = 2 },
			new TOCEntry() { Name = "Taking the job", Page = 2 },
			new TOCEntry() { Name = "Setting out on the trail", Page = 3 },
			new TOCEntry() { Name = "Trail to the Keyhole", Page = 3 },
			new TOCEntry() { Name = "Camp on the ridge", Page = 4 },
			new TOCEntry() { Name = "The valley", Page = 4 },
			new TOCEntry() { Name = "The Greenhouse", Page = 5, Children = new TOCEntry[] {
				new TOCEntry() { Name = "Exterior", Page = 5 },
				new TOCEntry() { Name = "Top Floor", Page = 5 },
				new TOCEntry() { Name = "Basement", Page = 6 },
				new TOCEntry() { Name = "Cave", Page = 6 }
			}}
		}},
		new TOCEntry() { Name = "Credits", Page = 8 }
	};
	
	using (var pdf = PdfReader.Open(FILENAME))
	{
		pdf.Info.Title = "A Mysterious Tune - Ryuutama Adventure";
		pdf.Outlines.Clear();
		BuildTocs(BOOKMARKS, pdf);
		pdf.Save(FILENAME);
	}
}

void BuildTocs(TOCEntry[] tocs, PdfDocument pdf, PdfOutline parent = null)
{
	foreach (var toc in tocs) BuildToc(toc, pdf, parent);
}

void BuildToc(TOCEntry toc, PdfDocument pdf, PdfOutline parent = null)
{
	var bookmark = (parent != null ? parent.Outlines : pdf.Outlines).Add(toc.Name, pdf.Pages[toc.Page], true);
	if (toc.Children != null) BuildTocs(toc.Children, pdf, bookmark);
}

class TOCEntry
{
	public string Name;
	public int Page;
	public TOCEntry[] Children;
}

Markdown made me a better writer

August 30, 2024

Is this a hot take? It feels like it, but maybe it’s common knowledge in 2024. Or maybe it’s a deranged thought that only makes sense if you’ve used vim regularly for any length of time.

Markdown ends up being my writing tool of choice these days, rapidly replacing Google Docs for most personal stuff. Several years ago, I worked on a decent-sized knowledge base where we used Markdown for article content; moving away from HTML took a lot of pain out of the writing and editing process. I write TTRPG supplements in Homebrewery (which has great theming, and also lets you tinker with CSS as needed) and then export to PDF. I take notes in Obsidian (general writing, sometimes moving into Homebrewery) and Joplin (notes synced to my phone, like shopping lists). It’s not just for posting comments.

Obivously, I’d use Word for something larger and more official like a requirements or design document, but for sheer productivity, you can’t beat it. It’s easier to get and stay in flow state because there’s fewer distractions.

Continue reading

Prepping TTRPG sessions for cons and gaming events

May 28, 2024

I’ve written before about my theory of running game sessions at conventions in the context of my experience running Hamacon, but I’ve refined that process in the past year in hopes of building up a library of one-shots I can run on-demand.

When I prep a one-shot for friends, I try to give Future Dylan notes for stuff that’s hard for me to improvise in the moment (it’s much easier to imagine yourself in a scene when you’re not wrangling a group of people). That’s only a few steps removed from creating reusable adventures–so I started asking, why not take the extra step? (And that’s only a few steps removed from writing published adventures, which is an entirely different topic.)

My goals are:

  • The module should be repeatable. It should only take minimal work to “refresh” it the next time it’s run.
  • The module should be complete. Everything that’s needed should be in the folder (except for larger physical bits like dice, tokens, etc., which are usually common anyway).
  • Above all else, the module preparation should respect players’ time. A four-hour gaming session carries a huge opportunity cost at a con. You should make good use of the time they’ve given you (and give them as much of it back as you can without sacrificing the experience).

Everything gets stored in a pocket folder

It’s smaller and more convenient than a binder, but can collect everything you need.

Each folder is a discrete packet of information–everything you need to run an adventure (aside from more physical stuff like books, GM screens, dice, battlemats, etc.) is in there, at your fingertips.

Continue reading

Being a Better TTRPG Player part 4: Take People Problems Out-of-Game

December 28, 2023

RPGs are traditionally a game enjoyed by the socially awkward. For many of us, that means we don’t like interpersonal conflict.

I suspect my experience is common: in college, I thought mastering the rules of D&D 3.5 would give me the ability to make peace between killer DMs, power gamers, and other ne’er-do-wells by holding them accountable legalistically.

As a 40-something, that sure looks like conflict avoidance.

Don’t underestimate the power of pulling someone aside and diplomatically telling them they’re being a jerk. (And you don’t even have to be the gamemaster to do it!)

Furthermore, don’t underestimate the power of refusing to play with people who continue to be jerks after that.

For a lot of us, this seems unconscionable. Usually, you’re playing long-running games with your friends, after all. (And here the Five Geek Social Fallacies come into play, distorting how we think about those concepts.)

But life’s too short to play games with people you don’t or can’t trust. You can’t build strong enough mechanical guardrails to fix a lack of trust.

Continue reading

Being a Better TTRPG Player part 3: Read Between the Lines

December 21, 2023

(Read part 2 here)

This is going to sound like metagaming, but if done in good faith, it isn’t.

When you get engrossed in a game world, it’s easy to forget that it’s not a real place–it only exists in the game master’s head. You don’t get information from that world like you would from the real world, or even a video game world.

The GM has their own incentives for how they present information to you.

Continue reading

Being a Better TTRPG Player part 2: Actively Share the Spotlight

December 14, 2023

(Read part 1 here)

A good gaming experience is one where every player gets to do something interesting. (And not every player will define “interesting” the same way.)

As a player, you can have as much control over the spotlight as the gamemaster does, for good or ill. It’s important to use it wisely, and not just expect the GM to manage it.

Continue reading

Being a Better TTRPG Player part 1: Read the Table

December 10, 2023

Over the last several years, I feel like I’ve hit a second wind with tabletop gaming. An obvious factor is the trend towards lighter, more storytelling-focused systems that remove friction–which has even shown up in newer editions of popular games like D&D 5th Edition.

But so much of it is simply being more emotionally intelligent than I was in my teens, 20’s, or early 30’s.

There are a lot of tips on being a better Dungeon/Game Master out there, but I’ve also really enjoyed moments that have taught me how to be a better player. This is a series covering a collection of those observations.

Continue reading

Recording a panel video with free software

May 18, 2020

After MomoCon moved online, I had to record a video for my panel, The Joy of Game Development. Since this was a live coding demo, I needed to capture my screen and vocal narration. To make it feel more like a panel, I also captured my webcam.

I used five pieces of free software to accomplish this:

Continue reading

Ludum Dare 46: Procedurally generating potted plants in Unity

May 11, 2020

For Ludum Dare 46, I created a very simple tamagotchi game called “Potted Plant Simulator” in Unity (you can find the code here). We kicked around this idea for “Keep it Alive” at the Knox Game Design online meetup, but it was more or less a joke.

However, brainstorming brought me back to the idea because I started wondering about how to procedurally generate a plant in Unity. I speculated that Unity’s hierarchy system (which localizes position, scale, and rotation to the parent node) would let you chain together branches, and I had to test that hypothesis. (Spoiler: hierarchy does not seem to be the best way to handle this.)

Continue reading

Running a Board Gaming Event: Games You Might Not Have Tried

August 11, 2019

Continue reading
×