Вопрос: Как создать и сохранить PDF файл с особым форматированием текста?

Лучший ответ:
- "откусывайте" нужное количество элементов от массива
- печатаете то что "откусили"
- переходите на новую строку
- повторяете пока не закончился массив

UPD.:
Ну раз Сергей Горностаев расщедрился на код, то я добавлю свой вариант:
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

class Scratch {

    public static void main(String[] args) {
        int[] ints = new int[]{13, 24, 24, 65, 3, 32, 27, 77, 88, 25, 74, 14};

        batches(Arrays.stream(ints).boxed().toList(), 3)
                .map(list -> list.stream().map(String::valueOf).collect(Collectors.joining(" ")))
                .reduce((s1, s2) -> s1 + System.lineSeparator() + s2)
                .ifPresent(System.out::println);
    }

    private static <T> Stream<List<T>> batches(List<T> source, int length) {
        var size = source.size();
        if (length <= 0 || size <= 0) {
            return Stream.empty();
        }
        var fullChunks = (size - 1) / length;
        return IntStream.range(0, fullChunks + 1).mapToObj(
                n -> source.subList(n * length, n == fullChunks ? size : (n + 1) * length));
    }
}

Худший ответ:
class Demo {
    public static <T> List<List<T>> chunkify(List<T> data, int n) {
        List<List<T>> chunks = new ArrayList<>();

        for (int i = 0; i < data.size(); i += n) {
            chunks.add(data.subList(i, Math.min(data.size(), i + n)));
        }

        return chunks;
    }

    public static <T> List<List<T>> transpose(List<List<T>> data) {
        final int n = data.stream()
            .mapToInt(List::size)
            .max()
            .orElse(-1);

        List<Iterator<T>> iterators = data.stream()
            .map(List::iterator)
            .collect(Collectors.toList());

        return IntStream.range(0, n)
            .mapToObj(unused -> iterators.stream()
                .filter(Iterator::hasNext)
                .map(Iterator::next)
                .collect(Collectors.toList()))
            .collect(Collectors.toList());
    }

    public static String makeTable(List<List<Integer>> data) {
        String format = data.stream()
            .flatMap(List::stream)
            .max(Comparator.comparing(identity()))
            .map(Objects::toString)
            .map(String::length)
            .map(fieldLength -> "%-" + (fieldLength + 1) + "d")
            .orElse("%d ");

        String table = "";
        for (int x = 0; x < data.size(); x++) {
            List<Integer> chunk = data.get(x);
            for (int y = 0; y < chunk.size(); y++) {
                table += String.format(format, chunk.get(y));    
            }
            table += "\n";
        }
        return table;
    }

    public static void do() {
        // Тестовые данные
        List<Integer> numbers = new Random()
            .ints(50, 1, 1000)
            .boxed()
            .collect(Collectors.toList());

        // Разбиваем на блоки по 15 элементов
        List<List<Integer>> chunks = chunkify(numbers, 15);

        // Транспонируем
        List<List<Integer>> transposed = transpose(chunks);

        // Форматируем в строку
        String table = makeTable(transposed);

        // Записываем строку в файл
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("table.txt"))) {
            writer.write(table);
        }
    }
}
Как выводить в pdf - зависит от того, какую библиотеку для работы с pdf вы будете использовать.