Skip to content
academia.sh

Lesson 21 / 26

Text Formatting

Whitespace processing rules, alignment and indentation, text-transform's dependence on language, and controlling underline decoration.

Contents

The font has arrived and the size scale is built. Decisions on the text itself remain: what happens to whitespace in the source text, how lines get aligned, how letters get converted, how links get underlined.

The first of these decisions is the most surprising one. In the document written in the Web Fundamentals and HTML curriculum, the text was written indented, and the indentation did not show up on screen. This lesson shows the reason for that, with a rule and a calculation.

Whitespace Processing

In the default behavior, consecutive whitespace characters in the source text get collapsed into a single space, and line breaks count as a space character too. This is what makes the HTML document formattable: indenting, line-splitting, and alignment happen freely in the source text without affecting the output.

The rule gets changed with the white-space property. The program below applies five values to the same source text.

// whitespace.mjs — how white-space values process the same source text
const SOURCE = "North   Slope\n  Station\tmeasurement   readings";

const BEHAVIOR = {
  "normal":   { collapseSpaces: true,  preserveBreaks: false, wrap: true  },
  "nowrap":   { collapseSpaces: true,  preserveBreaks: false, wrap: false },
  "pre":      { collapseSpaces: false, preserveBreaks: true,  wrap: false },
  "pre-wrap": { collapseSpaces: false, preserveBreaks: true,  wrap: true  },
  "pre-line": { collapseSpaces: true,  preserveBreaks: true,  wrap: true  },
};

function process(text, rule) {
  let s = text;
  if (rule.preserveBreaks) {
    if (rule.collapseSpaces) s = s.split("\n").map((p) => p.replace(/[ \t]+/g, " ").trim()).join("\n");
  } else {
    s = s.replace(/\s+/g, " ").trim();
  }
  return s;
}

console.log("source (with escape sequences):");
console.log(JSON.stringify(SOURCE));
console.log("");
for (const [name, rule] of Object.entries(BEHAVIOR)) {
  const result = process(SOURCE, rule);
  console.log(`${name.padEnd(9)} wrap=${String(rule.wrap).padEnd(5)} -> ${JSON.stringify(result)}`);
}

console.log("\n--- text-transform: the Turkish special case ---");
const sample = "istasyon ILIK ışık";
console.log(`source            : ${sample}`);
console.log(`uppercase (en)    : ${sample.toLocaleUpperCase("en-US")}`);
console.log(`uppercase (tr)    : ${sample.toLocaleUpperCase("tr-TR")}`);
console.log(`lowercase (en)    : ${sample.toLocaleLowerCase("en-US")}`);
console.log(`lowercase (tr)    : ${sample.toLocaleLowerCase("tr-TR")}`);

console.log("\n--- text-indent and alignment effect (60-character box) ---");
const BOX = 60;
const line = "Station sits at 1840 meters above sea level";
for (const [name, align] of [["start", (s) => s.padEnd(BOX)], ["end", (s) => s.padStart(BOX)], ["center", (s) => s.padStart((BOX + s.length) / 2).padEnd(BOX)]]) {
  console.log(`${name.padEnd(7)}|${align(line)}|`);
}
source (with escape sequences):
"North   Slope\n  Station\tmeasurement   readings"

normal    wrap=true  -> "North Slope Station measurement readings"
nowrap    wrap=false -> "North Slope Station measurement readings"
pre       wrap=false -> "North   Slope\n  Station\tmeasurement   readings"
pre-wrap  wrap=true  -> "North   Slope\n  Station\tmeasurement   readings"
pre-line  wrap=true  -> "North Slope\nStation measurement readings"

--- text-transform: the Turkish special case ---
source            : istasyon ILIK ışık
uppercase (en)    : ISTASYON ILIK IŞIK
uppercase (tr)    : İSTASYON ILIK IŞIK
lowercase (en)    : istasyon ilik ışık
lowercase (tr)    : istasyon ılık ışık

--- text-indent and alignment effect (60-character box) ---
start  |Station sits at 1840 meters above sea level                 |
end    |                 Station sits at 1840 meters above sea level|
center |        Station sits at 1840 meters above sea level         |

The output distinguishes two axes: whether whitespace gets collapsed, and whether line-splitting gets allowed.

normal — consecutive spaces collapse to one, line breaks count as a space, the line gets split to fit the box. The three spaces and the tab character in the source text turned into a single space.

nowrap — does the same whitespace processing but does not allow line-splitting. The text overflows the box; the situation established in the Overflow Management lesson shows up.

pre — changes nothing: whitespace and line breaks are kept exactly as they are, and no automatic line-splitting happens. This is the browser’s default style for the pre element.

pre-wrap — keeps whitespace and line breaks but splits lines that do not fit the box. This value gets written instead of pre when long lines need showing without overflowing.

pre-line — collapses whitespace but keeps line breaks. It gets used to show user text coming from a text field with its paragraph structure.

Letter Conversion Depends on Language

The output’s second block shows why the text-transform property cannot be considered a purely visual conversion.

The word istasyon becomes ISTASYON when uppercased by English rules, and İSTASYON when uppercased by Turkish rules. The word ILIK becomes ilik when lowercased by English rules, and ılık by Turkish rules — two different words.

The reason is that in the Turkish alphabet, i and ı are separate letters, and their uppercase counterparts are İ and I. The code point distinction from the How Computers Work curriculum turns into a difference in meaning here.

This shows that writing text-transform depends on the document’s lang declaration. In a document where lang="tr" has not been declared on the root element, converting to uppercase can give a wrong result.

A second warning: the conversion happens only in drawing. The text in the document does not change; copied text comes in its source form, and a screen reader reads its source form. If a heading needs to appear in uppercase, this is a visual decision and gets made in the style sheet; the text itself should stay in its ordinary spelling in the source.

Alignment

text-align takes five basic values: start, end, center, justify, and their physical counterparts left and right. The logical values follow the writing direction, and that is the form used in this course.

The output’s third block shows three alignments in a fixed-width box. The decision gets made by three criteria.

Centering works for short text; in long paragraphs, each line’s starting point shifts, making it harder for the eye to find the start of a line. It suits headings and short captions, not body text.

Justification (justify) makes every line the same length by widening the word spacing. In narrow columns, the spacing widens excessively and vertical white streaks form inside the text. The result is more balanced when used together with hyphens: auto, but left alignment still gets preferred in narrow columns.

Indentation (text-indent) pulls paragraph starts inward. If vertical spacing exists between paragraphs, indentation is unnecessary; when both get used together, the paragraph break gets marked twice. In print tradition, indentation stands in for spacing.

Text Decoration

text-decoration is a shorthand and sets four longhands: -line (where the line sits), -color, -style, -thickness.

Links being underlined comes from the browser default and is not a visual decoration: marking a link within text with color alone makes the link invisible to a reader who cannot perceive color distinctions. The underline should not get removed unless another visual distinguisher gets put in its place.

The underline’s legibility gets adjusted with text-underline-offset and text-decoration-skip-ink; the second ensures the line does not cut through letters’ descenders.

/* station.css — step 20: text formatting */
.measurement-table th {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--font-small);
}

.measurement-raw-data {
  white-space: pre-wrap;
  font-family: ui-monospace, monospace;
  tab-size: 2;
}

.observation-note { white-space: pre-line; }

main a {
  text-decoration-line: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
  text-decoration-skip-ink: auto;
}

main a:hover { text-decoration-thickness: 2px; }

The .measurement-raw-data class shows the raw measurement record coming from the station: line breaks and alignment spaces carry meaning, so they get preserved; writing pre-wrap instead of pre makes long lines split instead of overflow.

The letter spacing given to the header cells is a standard correction for uppercase-transformed text: capital letters look more cramped than lowercase, and reading gets harder if the spacing does not widen.

Summary

  • In the default whitespace processing, consecutive spaces collapse to one and line breaks count as a space; the source text’s indentability rests on this.
  • white-space sets two axes at once: whether whitespace gets collapsed, and whether line-splitting gets allowed. pre-wrap keeps whitespace and splits the line, pre-line collapses whitespace and keeps line breaks.
  • text-transform depends on language; because i and ı have separate uppercase counterparts in Turkish, a wrong conversion can result without a lang declaration.
  • The conversion happens only in drawing; the text in the document does not change, and comes in its source form when copied.
  • A link’s underline is not a decoration, it is a color-independent distinguisher; if it gets removed, another visual marker has to get put in its place.

Next Step

The text decisions are complete. The back of the boxes has not been looked at yet: a box’s background can be not just a color, but a positionable and repeatable image too. The next lesson takes up background layers, positioning and repetition rules, and which region the background extends to.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close