Simple text reader

I made a simple text reader for myself, and thought maybe someone else would like it as well, so here you go:

Reader.pdx.zip (25.5 KB)

Features:
-Loads txt-files from the data folder (net.diefonk.Reader)
-Scroll with crank or d-pad
-Automatically saves your position (on pause/lock/exit)

Have the source code and this lightly edited Wikipedia article as a bonus:
main.lua.zip (1.5 KB)
Magic (supernatural).txt.zip (24.1 KB)

9 Likes

With the screen being so nice, I wondered myself how it’d be to maybe use my playdate at the beach to read small novellas. Thank you for that!

1 Like

Hi! That is great idea! I really wanted to use Playdate as a reader because of screen and portability.

Just letting you know that i had an error opening big txt book. Run loop stalled for more than 10 seconds

It does work in simulator though.

1 Like

Slower performance on the hardware can cause this “run loop stalled” issue. (We kill any game that runs for 10 seconds without relinquishing control to the OS. This problem often doesn’t show up in the Simulator because the Simulator is so darn fast.)

Lua coroutines can be a useful way to solve the issue of completing a computationally intensive process while still giving control back to the OS once a frame. (It’s also a great way to do things like show a progress bar while the document is being parsed.) These are described in Inside Playdate, but I’d be happy to answer any questions here if you have them.

2 Likes

I did some tests to see what the largest file size to work on the device was, and it turned out to be about 65 kilobytes.

Then I worked on improving that by splitting the loading into chunks so it could be done over several frames. Through this I managed to get the maximum file size up to about 140 kilobytes. The way I do the rendering is that I draw the text to an image once and then just re-draw the image when you scroll. I thought that maybe I could increase the maximum file size further by splitting the text into multiple images, but I haven’t tried that yet.

What I did try was scrolling to the bottom of a file that was 140 kilobytes, and I noticed that the image wasn’t as tall as it was supposed to be. By printing the intended image height and the actual image height I discovered that the maximum file size was now actually 61 kilobytes, as files larger than that would make the image smaller than it needed to be. Seems to me like creating an image that would require more memory than what’s available just makes it smaller than intended.

So now I’m not sure that splitting the text into multiple images would actually work either. Maybe if you create and delete images while scrolling, or maybe if you only draw a substring that fills the display. It might be a challenge to make that smooth though.

1 Like

setRefreshRate should take care of that for you, it'll wait until the next update if your code finishes early. If your code finishes late then you simply need to pick a lower refresh rate.

It's an interesting challenge to only draw as much as can be seen. The challenge then would be to scan the text and figure out which section is one screen. I'm guessing you can draw text it all and let the viewport take care of what section to display?

I meant it’d be difficult to make it scroll continuously, like it does now, when only drawing a bit of text at a time.

Drawing all the text is actually what takes a long time (more than 10 seconds above 140 kB), which is why I only draw it once to an image and then just draw the image. Simply drawing all the text directly has a very low framerate even in the simulator, so that won’t work.

I think the trick will have to be some way to figure out what part of the string you need to draw and only draw that, while also making it scroll continuously and not shift or jump around. The answer still eludes me, but I feel it must be out there.

1 Like

This reminds me of the old “scrollers” scroll texts in demo scene productions back on my Atari ST. Of course, with that they only had to draw a short range from of a long line of text. But I think they solved a similar problem back then. If they split a line of text into letters for presentation horizontally, you are splitting a body of text into lines for presentation vertically.

If the text was split into lines, then it would be straightforward to figure out which lines need displaying on screen and then you would only need to draw the “next” line slightly ahead of time, append it to the image. Or you could just drawtext all lines on screen every frame, because there aren’t many, rather than manage an intermediate image.

Hmm. Maybe playdate.graphics.font:getTextWidth(text) could be used at successive word breaks to figure out where lines wrap. This would mean time would be spent pre-processing the text file on loading. Afterwards you could cache/save the processed results (as a serialised table) so that it doesn’t need to be done again (unless the text or font changes).

The benefit of such an approach would be that you could allow line-height, etc to be chosen by the user, and have changes displayed instantly.

1 Like

I actually had that same idea while going to bed yesterday, so seeing you have it as well made me extra confident in it.

And it worked very well:
Reader.pdx.zip (21.3 KB)
main.lua.zip (1.9 KB)

One thing to note if you’ve used the previous version is that bookmarks are now done via table index instead of position, so you should delete the old bookmarks file. Loading is also slower now, but it’s only done the first time you open a file, and once you have the processed file the original file isn’t needed anymore. This means that for big files you could always do the loading in the simulator and then just transfer the resulting json-file to your device.

Hope this version works for you, @aronegal.

1 Like

nicely done!

some quick optimisations:

  • you’re doing processing in playdate.update() which is limited by refresh rate, so if you do playdate.display.setRefreshRate(0) when you set state = loading and then set it back to something more normal when you set state = reading then loading and processing is much, much faster!

  • move expensive calls out of your while loops

  • & create local alias of calls you do multiple times

		local getfont <const> = gfx.getFont()
		while index <= #text do
			if getfont:getTextWidth(text[index]) > 390 then
1 Like

Thanks for that! Seems a bit faster now.

Reader.pdx.zip (19.3 KB)

I wanted to test a really big file, so I downloaded the entire Bible as a txt and set it to load in my simulator (took a quite a while). It opens relatively quickly on device, but scrolling isn’t completely smooth for some reason. At least it’s not so bad it’s unusable, and I don’t think most people are going to read such big files. If anyone wants to try it: bible.txt.json.zip (1.4 MB)

When you draw the text you have a formula that adds some line height:
fontHeight + 5

But when you set textPosition you only use
fontHeight

So for smooth scrolling do:
textPosition += fontHeight+5
and
textPosition -= fontHeight+5

new features…

On menu Press B to delete JSON file of selected book for testing.

function pd.BButtonUp()
	if state == menu then
		pd.datastore.delete(files[selectedFile])
	end
end

Jump to start/end

if pd.buttonJustPressed("left") then
	textPosition = 0
	textIndex = 1
	drawText()
elseif pd.buttonJustPressed("right") then
	textPosition = 0
	textIndex = #text-9
	drawText()
end
1 Like

A whole week later, but here are the latest features:
- Jump to start/end with left/right
- Hold B button in menu to delete loaded file (+ bookmark)
- Hold A button while reading to edit scroll speeds
- Hold B button while reading to edit margins
- README file explaining all of the above

Reader0.3.zip (28.8 KB)

5 Likes

Creating json in simulator worked like a charm! Thanks!

1 Like

Just released this on itch.io, as well as put the source code up on GitHub. New name. Hope you like it!
Pocket Reader by Diefonk
GitHub - Diefonk/pocket-reader: Simple reader app for Playdate

1 Like