diff --git a/pdf.c b/pdf.c
index 93741d6854105c297403722cfc7f6021ac7684f2..363c08b75f1f3fa5e23659e17059f1d18a234461 100644
--- a/pdf.c
+++ b/pdf.c
@@ -2,7 +2,7 @@
  * pesco 2019
  */
 
-#include <string.h>	/* strncmp(), memset() */
+#include <string.h>	/* strncmp(), memset(), memcpy() */
 
 #include <hammer/hammer.h>
 #include <hammer/glue.h>
@@ -393,7 +393,45 @@ act_a85partialgroup(const HParseResult *p, void *u)
 	return H_MAKE_BYTES(bytes, bytes_used);
 }
 
-// TODO: flatten sequence in a85string semantic action
+HParsedToken *
+act_a85string(const HParseResult *p, void *u)
+{
+	uint8_t *result_bytes;
+	size_t chunk_number;
+	size_t required_bytes;
+	size_t out_pos = 0;
+	HCountedArray *seq = H_CAST_SEQ(p->ast);
+	HParsedToken *res;
+
+	/* Number of 4-byte chunks, minus the potential last partial group and EOD */
+	chunk_number = seq->used - 2;
+
+	/* Special-case: last chunk before EOD may be 4, 3, 2 or 1 bytes
+	 * The latter two happening if the group was parsed from a partial
+	 * group consisting less than 5 chars */
+	HBytes *last_chunk = seq->elements[seq->used-1];
+	required_bytes = chunk_number * 4 + last_chunk->len;
+
+	result_bytes = h_arena_malloc(p->arena, required_bytes);
+
+	/* Memcpy all but the group's bytes into a single array */
+	for (size_t i = 0; i < seq->used-1; ++i)
+	{
+		HBytes *chunk = H_CAST_BYTES(seq->elements[i]);
+		assert(out_pos < required_bytes);
+		memcpy(&(result_bytes[out_pos]), chunk->token, 4);
+		out_pos += 4;
+		assert(out_pos < required_bytes);
+	}
+
+	memcpy(&(result_bytes[out_pos]), last_chunk->token, last_chunk->len);
+	out_pos += last_chunk->len;
+	/* We should have filled the array exactly by this point */
+	assert(out_pos == required_bytes-1);
+
+	res = H_MAKE_BYTES(result_bytes, required_bytes);
+	return res;
+}
 
 HParsedToken *
 act_nat(const HParseResult *p, void *u)
@@ -870,7 +908,7 @@ init_parser(struct Env *aux)
 	H_VARULE(a85partial4group,	h_repeat_n(MANY_LWS(a85digit), 4));
 	H_ARULE(a85partialgroup, CHX(a85partial4group, a85partial3group, a85partial2group));
 
-	H_RULE(a85string,	SEQ(h_many(a85group), OPT(a85partialgroup), IGN(a85eod)));
+	H_ARULE(a85string,	SEQ(h_many(a85group), OPT(a85partialgroup), IGN(a85eod)));
 
 	/* AsciiHexDecode parser */
 	H_RULE(ahexeod,	h_ch('>'));