}
 
 impl HorzAlign {
-    fn for_mixed(var_type: VarType) -> Self {
+    pub fn for_mixed(var_type: VarType) -> Self {
         match var_type {
             VarType::Numeric => Self::Right,
             VarType::String => Self::Left,
     }
 }
 
+impl AsValueOptions for &ValueOptions {
+    fn as_value_options(self) -> ValueOptions {
+        *self
+    }
+}
+
 impl AsValueOptions for ValueOptions {
     fn as_value_options(self) -> ValueOptions {
         self
         self.options.small
     }
 
-    fn var_type(&self) -> VarType {
+    pub fn var_type(&self) -> VarType {
         match self.inner {
             ValueInner::Number { .. } if self.show_label.is_none() => VarType::Numeric,
             _ => VarType::String,
 
 use crate::output::table::{CellInner, Table};
 
 use super::{
-    Area, Axis, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category, CategoryTrait, Color,
-    Coord2, Dimension, Footnote, PivotTable, Rect2, RowColBorder, Stroke, Value,
+    Area, AsValueOptions, Axis, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category,
+    CategoryTrait, Color, Coord2, Dimension, Footnote, PivotTable, Rect2, RowColBorder, Stroke,
+    Value,
 };
 
 /// All of the combinations of dimensions along an axis.
             Coord2::new(0, 0),
             self.look.areas.clone(),
             self.borders(false),
+            self.as_value_options(),
         );
         for (y, row) in rows.enumerate() {
             table.put(
             self.axes[Axis3::X].label_depth,
         );
         let n = EnumMap::from_fn(|axis| data[axis] + stub[axis]).into();
-        let mut body = Table::new(n, stub, self.look.areas.clone(), self.borders(printing));
+        let mut body = Table::new(
+            n,
+            stub,
+            self.look.areas.clone(),
+            self.borders(printing),
+            self.as_value_options(),
+        );
         compose_headings(
             &mut body,
             &self.axes[Axis3::X],
 
 
 use super::pivot::{
     AreaStyle, Axis2, BorderStyle, Coord2, Footnote, Look, PivotTable, Rect2, Stroke, ValueInner,
+    ValueOptions,
 };
 use super::table::{CellInner, Content, Table};
 
     pub style: &'a AreaStyle,
     pub subscripts: &'a [String],
     pub footnotes: &'a [Arc<Footnote>],
+    pub value_options: &'a ValueOptions,
 }
 
 impl<'a> DrawCell<'a> {
             style,
             subscripts,
             footnotes,
+            value_options: &table.value_options,
         }
     }
 }
 
 
 use crate::output::pivot::Coord2;
 
-use super::pivot::{Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value};
+use super::pivot::{
+    Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value, ValueOptions,
+};
 
 #[derive(Clone)]
 pub struct CellRef<'a> {
 
     /// Horizontal and vertical rules.
     pub rules: EnumMap<Axis2, Vec<Border>>,
+
+    /// How to present values.
+    pub value_options: ValueOptions,
 }
 
 impl Table {
         headers: Coord2,
         areas: EnumMap<Area, AreaStyle>,
         borders: EnumMap<Border, BorderStyle>,
+        value_options: ValueOptions,
     ) -> Self {
         Self {
             n,
                 Axis2::X => vec![Border::Title; (n.y() + 1) * n.x()],
                 Axis2::Y => vec![Border::Title; n.y() * (n.x() + 1)],
             },
+            value_options,
         }
     }
 
 
 
 use super::{
     driver::Driver,
-    pivot::{Axis2, BorderStyle, Coord2, PivotTable, Rect2, Stroke},
+    pivot::{Axis2, BorderStyle, Coord2, DisplayValue, HorzAlign, PivotTable, Rect2, Stroke},
     render::{Device, DrawCell, Pager, Params},
     table::Content,
-    text_line::TextLine,
+    text_line::{clip_text, TextLine},
     Details, Item,
 };
 
         }
     }
 
-    fn cell_to_text(cell: &DrawCell) -> String {
+    fn display_cell<'a>(cell: &DrawCell<'a>) -> DisplayValue<'a> {
         cell.inner
-            .display(() /* XXX */)
+            .display(cell.value_options)
             .with_font_style(&cell.style.font_style)
             .with_subscripts(cell.subscripts)
             .with_footnotes(cell.footnotes)
-            .to_string()
     }
 
     fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2, clip: Rect2) -> Coord2 {
         use Axis2::*;
         let mut breaks = new_line_breaks(text, bb[X].len());
         let mut size = Coord2::new(0, 0);
-        for (text, y) in breaks.zip(bb[Y].clone()) {
+        for (text, _y) in breaks.zip(bb[Y].clone()) {
             let width = text.width();
             if width > size[X] {
                 size[X] = width;
             }
             size[Y] += 1;
+        }
+        size
+    }
 
-            if !clip[Y].contains(&y) {
-                continue;
-            }
-
-            
+    fn reserve(&mut self, y: usize) {
+        if y >= self.lines.len() {
+            self.lines.resize(y + 1, TextLine::new());
         }
-        todo!()
     }
 }
 
         match &item.details {
             Details::Chart => todo!(),
             Details::Image => todo!(),
-            Details::Group(vec) => todo!(),
+            Details::Group(_) => todo!(),
             Details::Message(diagnostic) => todo!(),
-            Details::PageBreak => todo!(),
+            Details::PageBreak => (),
             Details::Table(pivot_table) => self.output_table(pivot_table),
             Details::Text(text) => todo!(),
         }
     }
 
     fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] {
-        let text = Self::cell_to_text(cell);
+        let text = Self::display_cell(cell).to_string();
         let max_width = self.layout_cell(
             cell,
             &text,
     }
 
     fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize {
-        let text = Self::cell_to_text(cell);
+        let text = Self::display_cell(cell).to_string();
         self.layout_cell(
             cell,
             &text,
 
     fn draw_cell(
         &mut self,
-        draw_cell: &DrawCell,
-        alternate_row: bool,
+        cell: &DrawCell,
+        _alternate_row: bool,
         bb: &Rect2,
         valign_offset: usize,
         spill: EnumMap<Axis2, [usize; 2]>,
         clip: &Rect2,
     ) {
-        todo!()
+        let display = Self::display_cell(cell);
+        let text = display.to_string();
+        let horz_align = cell
+            .style
+            .cell_style
+            .horz_align
+            .unwrap_or_else(|| HorzAlign::for_mixed(display.var_type()));
+
+        use Axis2::*;
+        let mut breaks = new_line_breaks(&text, bb[X].len());
+        for (text, y) in breaks.zip(bb[Y].start + valign_offset..bb[Y].end) {
+            let width = text.width();
+            if !clip[Y].contains(&y) {
+                continue;
+            }
+
+            let x = match horz_align {
+                HorzAlign::Right | HorzAlign::Decimal { .. } => bb[X].end - width,
+                HorzAlign::Left => bb[X].start,
+                HorzAlign::Center => (bb[X].start + bb[X].end - width + 1) / 2,
+            };
+            let Some((x, text)) = clip_text(&text, &(x..x + width), &clip[X]) else {
+                continue;
+            };
+
+            self.reserve(y);
+            self.lines[y].put(x, text);
+        }
     }
 
     fn scale(&mut self, factor: f64) {
 
 ///
 /// Designed to make appending text fast, and access and modification of other
 /// column positions possible.
-#[derive(Default)]
+#[derive(Clone, Default)]
 pub struct TextLine {
     /// Content.
     string: String,
         }
     }
 }
+
+pub fn clip_text<'a>(
+    text: &'a str,
+    bb: &Range<usize>,
+    clip: &Range<usize>,
+) -> Option<(usize, &'a str)> {
+    let mut x = bb.start;
+    let mut width = bb.len();
+
+    let mut iter = text.chars();
+    while x < clip.start {
+        let c = iter.next()?;
+        if let Some(w) = c.width() {
+            x += w;
+            width = width.checked_sub(w)?;
+        }
+    }
+    if x + width > clip.end {
+        if x >= clip.end {
+            return None;
+        }
+
+        while x + width > clip.end {
+            let c = iter.next_back()?;
+            if let Some(w) = c.width() {
+                width = width.checked_sub(w)?;
+            }
+        }
+    }
+    Some((x, iter.as_str()))
+}