How to fix vertical text filling in PDF?

Fixing PDF Forms That Show Sideways / Vertical Text When Filled

Some PDF forms (often government forms such as LDSS-4411) are saved as landscape pages stored rotated 90°. Each form field also carries a matching 90° rotation flag. When you fill the form with a tool that ignores that flag, your typed text is written in the page's true orientation and ends up rotated sideways / vertical.

The fix is to flatten the rotation: bake the page rotation into the page content, reset each page to 0°, move every field box into the natural orientation, and clear the per-field rotation flags. After this, any fill tool writes upright, horizontal text. The visible layout, field names, and any existing values are unchanged.


Requirements

  • Python 3.8+
  • The pypdf library:
pip install pypdf

Python script

Save this as fix_rotated_pdf_form.py.

#!/usr/bin/env python3
"""
fix_rotated_pdf_form.py

Flattens page rotation in a PDF form so filled-in text renders horizontally
instead of sideways. Layout, field names, and values are preserved.

Usage:
    python fix_rotated_pdf_form.py input.pdf
    python fix_rotated_pdf_form.py input.pdf -o output.pdf

Requires: pypdf  ->  pip install pypdf
"""
import argparse
import sys

try:
    from pypdf import PdfReader, PdfWriter, Transformation
    from pypdf.generic import (
        NameObject, NumberObject, ArrayObject, FloatObject,
        IndirectObject, BooleanObject,
    )
except ImportError:
    sys.exit("pypdf is required. Install it with:  pip install pypdf")


def _resolve(obj):
    return obj.get_object() if isinstance(obj, IndirectObject) else obj


def flatten_rotation(src_path, out_path):
    reader = PdfReader(src_path)
    writer = PdfWriter()
    writer.append(reader)

    changed_pages = 0

    for page in writer.pages:
        rot = int(page.get("/Rotate") or 0) % 360
        if rot == 0:
            continue

        box = [float(v) for v in page.mediabox]
        w = box[2] - box[0]
        h = box[3] - box[1]

        # Matrix that reproduces the visual rotation in un-rotated page space.
        if rot == 90:
            ctm = (0, -1, 1, 0, 0, w)
            new_w, new_h = h, w
        elif rot == 270:
            ctm = (0, 1, -1, 0, h, 0)
            new_w, new_h = h, w
        elif rot == 180:
            ctm = (-1, 0, 0, -1, w, h)
            new_w, new_h = w, h
        else:
            continue  # non-orthogonal rotation; skip safely

        page.add_transformation(Transformation(ctm))
        page.mediabox.lower_left = (0, 0)
        page.mediabox.upper_right = (new_w, new_h)
        if "/CropBox" in page:
            del page[NameObject("/CropBox")]
        page[NameObject("/Rotate")] = NumberObject(0)

        a, b, c, d, e, f = ctm

        def tx(x, y):
            return (a * x + c * y + e, b * x + d * y + f)

        annots = _resolve(page.get("/Annots")) or []
        for ann in annots:
            o = ann.get_object()
            if o.get("/Subtype") != "/Widget":
                continue
            r = [float(v) for v in o["/Rect"]]
            pts = [tx(r[0], r[1]), tx(r[2], r[3]), tx(r[0], r[3]), tx(r[2], r[1])]
            xs = [p[0] for p in pts]
            ys = [p[1] for p in pts]
            o[NameObject("/Rect")] = ArrayObject([
                FloatObject(min(xs)), FloatObject(min(ys)),
                FloatObject(max(xs)), FloatObject(max(ys)),
            ])
            mk = o.get("/MK")
            if mk is not None:
                mk = mk.get_object()
                if "/R" in mk:
                    mk[NameObject("/R")] = NumberObject(0)
            if "/AP" in o:
                del o[NameObject("/AP")]  # drop stale appearance

        changed_pages += 1

    acro = writer._root_object.get("/AcroForm")
    if acro is not None:
        acro = acro.get_object()
        acro[NameObject("/NeedAppearances")] = BooleanObject(True)

    with open(out_path, "wb") as fh:
        writer.write(fh)

    return changed_pages


def main():
    ap = argparse.ArgumentParser(
        description="Flatten page rotation in a PDF form so filled text is horizontal."
    )
    ap.add_argument("input", help="Path to the rotated PDF form")
    ap.add_argument("-o", "--output", help="Output path (default: <input>_fixed.pdf)")
    args = ap.parse_args()

    out = args.output or (args.input.rsplit(".", 1)[0] + "_fixed.pdf")
    n = flatten_rotation(args.input, out)
    if n:
        print(f"Fixed {n} rotated page(s). Saved: {out}")
    else:
        print(f"No rotated pages found; nothing to change. Copy saved: {out}")


if __name__ == "__main__":
    main()

Run it

# Creates LDSS-4411_fixed.pdf next to the input
python fix_rotated_pdf_form.py LDSS-4411.pdf

# Or choose your own output name
python fix_rotated_pdf_form.py LDSS-4411.pdf -o LDSS-4411_fixed.pdf

One-step bash wrapper (optional)

Save as fix_pdf.sh, then chmod +x fix_pdf.sh. It installs pypdf if needed and runs the fixer.

#!/usr/bin/env bash
# Usage: ./fix_pdf.sh input.pdf [output.pdf]
set -euo pipefail

if [ $# -lt 1 ]; then
  echo "Usage: $0 input.pdf [output.pdf]"
  exit 1
fi

IN="$1"
OUT="${2:-${1%.pdf}_fixed.pdf}"

# Ensure pypdf is available
python3 -c "import pypdf" 2>/dev/null || pip install --quiet pypdf

# Run the fixer (expects fix_rotated_pdf_form.py in the same folder)
DIR="$(cd "$(dirname "$0")" && pwd)"
python3 "$DIR/fix_rotated_pdf_form.py" "$IN" -o "$OUT"

echo "Saved: $OUT"
./fix_pdf.sh LDSS-4411.pdf

How to confirm it worked

Open the fixed PDF, type into any field, and the text should appear upright and horizontal. You can also check that the rotation is gone:

python3 - <<'PY'
from pypdf import PdfReader
r = PdfReader("LDSS-4411_fixed.pdf")
for i, p in enumerate(r.pages):
    print(f"page {i}: /Rotate = {p.get('/Rotate')}")
PY

Every page should now report /Rotate = 0.


Notes & troubleshooting

  • Nothing changes in the form itself. Only page orientation metadata and field box coordinates are adjusted. Static text, field names, and existing values stay identical.
  • "No rotated pages found" means the form was not rotated — the sideways text has a different cause (e.g. a font/encoding issue in your fill tool).
  • Already-filled PDFs: this script also works on filled forms, but if the bad (rotated) appearance was already baked in by another tool, re-open the fixed file in your form filler and re-save so appearances regenerate.
  • Works for 90°, 180°, and 270° rotations. Non-right-angle rotations are left untouched.
  • This is a generic fix for any rotated AcroForm PDF, not just LDSS-4411.