Enables modeling historically accurate Latin letterforms. Subtractive mark-making primitives for stone carving, wood engraving, and inscriptional letterforms.
This crate provides primitive types for modeling subtractive mark-making — where the tool removes material rather than depositing it. Unlike brush-path (flexible, additive) or pen-path (rigid, additive), chisel-path models the physics of carving into stone, wood, or metal.
Primitives only — rendering and simulation are handled by higher-level crates.
Roman capitals — the foundation of Latin typography — were created by a two-stage process:
- Brush: Letters painted on stone (giving thick/thin variation from brush angle)
- Chisel: Letters carved following the painted guides (adding precision, serifs, depth)
The serifs on Roman letters are not decorative — they are functional artifacts of how a stonemason cleanly terminates a chisel cut. The thick/thin contrast comes from the brush, but the crispness and characteristic serifs come from the chisel.
Most digital typefaces model only outlines. They miss the underlying physics that created those forms. Chisel-path captures why letters look the way they do.
mark-path (abstract)
├── additive-mark (deposits material)
│ ├── brush-path (flexible instrument)
│ └── pen-path (rigid instrument)
└── subtractive-mark (removes material)
├── chisel-path (stone, wood — this crate)
└── engrave-path (metal, fine work — future)
| Aspect | Brush/Pen (Additive) | Chisel (Subtractive) |
|---|---|---|
| Process | Deposits material | Removes material |
| Medium | Paper, silk, vellum | Stone, wood, metal |
| Dimension | 2D mark | 3D cut with depth |
| Light | Ink color | Shadow reveals form |
| Serifs | Stroke artifacts | Termination technique |
| Dynamics | Pressure, speed | Depth, approach angle |
- Lingenic Pen-Path — Point, Path, and geometry primitives
use chisel_path::{Chisel, ChiselProfile};
// Roman V-cut chisel (classic inscriptions)
let roman = Chisel::new()
.profile(ChiselProfile::V { angle: 60 })
.width(40);
// U-cut chisel (softer, more forgiving)
let soft = Chisel::new()
.profile(ChiselProfile::U { radius: 20 })
.width(50);
// Flat chisel (backgrounds, relief work)
let flat = Chisel::new()
.profile(ChiselProfile::Flat)
.width(80);| Profile | Description | Use |
|---|---|---|
V { angle } |
V-shaped cut | Roman inscriptions, sharp shadows |
U { radius } |
U-shaped (rounded) | Softer look, easier to carve |
Flat |
Flat bottom | Relief backgrounds, cleaning |
Unlike brush dynamics (pressure), chisel dynamics describe the cut:
use chisel_path::{CutDynamics, ApproachAngle};
let dynamics = CutDynamics::new()
.depth(30) // How deep into stone (centipoints)
.approach_angle(ApproachAngle::Perpendicular)
.with_grain(Direction::Along); // Cutting with/against grain| Parameter | Description |
|---|---|
depth |
How deep the cut (affects shadow) |
approach_angle |
Angle chisel enters stone |
grain_direction |
Relation to material grain |
material_hardness |
Affects what's achievable |
Serifs are termination techniques — ways to cleanly end a chisel stroke:
use chisel_path::SerifCut;
// Classic Roman triangular serif
let roman_serif = SerifCut::Triangular {
width: 60,
depth: 25,
};
// Bracketed (smooth transition to stem)
let bracketed = SerifCut::Bracketed {
width: 50,
curve_radius: 15,
};
// Slab (Egyptian style)
let slab = SerifCut::Slab {
width: 70,
thickness: 20,
};
// No serif (modern, or where strokes meet)
let none = SerifCut::None;The substrate affects what cuts are possible:
use chisel_path::{Stone, StoneType, Grain};
// Marble — fine grain, allows delicate work
let marble = Stone::new(StoneType::Marble)
.grain(Grain::Fine)
.hardness(0.6);
// Limestone — coarser, faster to carve
let limestone = Stone::new(StoneType::Limestone)
.grain(Grain::Medium)
.hardness(0.4);
// Granite — very hard, requires different technique
let granite = Stone::new(StoneType::Granite)
.grain(Grain::Coarse)
.hardness(0.9);
// Wood (for woodcut/engraving)
let boxwood = Stone::new(StoneType::Wood)
.grain(Grain::EndGrain) // End-grain for fine detail
.hardness(0.3);A cut is a path with chisel dynamics at each point:
use chisel_path::{CutSegment, CutPoint, Chisel};
use pen_path::{Point, Connector};
let segment = CutSegment {
point: CutPoint {
position: Point::new(100, 500),
depth: 30,
approach: ApproachAngle::Perpendicular,
},
connector: Connector::Line,
action: CutAction::Continue,
};Actions describe what happens at each point:
| Action | Description |
|---|---|
Entry |
Begin cut — chisel enters stone |
Continue |
Continue cut at same depth |
Deepen |
Increase depth |
Shallow |
Decrease depth |
CleanUp |
Finishing cut to clean edges |
SerifCut(SerifCut) |
Execute serif termination |
Exit |
End cut — chisel leaves stone |
use chisel_path::{ChiselStroke, StrokeBuilder, Chisel, SerifCut};
let chisel = Chisel::v_cut(60).width(40);
let stroke = StrokeBuilder::new(chisel)
// Entry with triangular serif
.entry_serif(SerifCut::Triangular { width: 60, depth: 25 })
.move_to(50, 0)
.depth(30)
// Main stem
.cut_to(50, 700)
// Exit with triangular serif
.exit_serif(SerifCut::Triangular { width: 60, depth: 25 })
.build();use chisel_path::{Glyph, StrokeBuilder, Chisel, SerifCut, Stone};
let chisel = Chisel::v_cut(60).width(40);
let stone = Stone::marble();
let a = Glyph::new('A')
.advance_width(700)
.on_stone(stone)
// Left diagonal stem
.stroke(
StrokeBuilder::new(chisel)
.entry_serif(SerifCut::Triangular { width: 50, depth: 25 })
.move_to(50, 0)
.depth(30)
.cut_to(350, 720)
.exit_serif(SerifCut::None) // Meets apex
.build()
)
// Right diagonal stem
.stroke(
StrokeBuilder::new(chisel)
.entry_serif(SerifCut::None) // Meets apex
.move_to(350, 720)
.depth(30)
.cut_to(650, 0)
.exit_serif(SerifCut::Triangular { width: 50, depth: 25 })
.build()
)
// Crossbar (shallower cut)
.stroke(
StrokeBuilder::new(chisel)
.move_to(150, 280)
.depth(20) // Shallower than stems
.cut_to(550, 280)
.build()
)
.build();The .inscriptional format describes carved letterforms:
.stone-begin marble
type Marble
grain Fine
hardness 0.6
.stone-end
.chisel-begin roman-v
profile V
angle 60
width 40
.chisel-end
.glyph-begin A U+0041
advance-width 700
# Left stem
.cut-begin roman-v
serif-entry triangular 50 25
depth 30
cut-path 50,0 ⏤ 350,720
serif-exit none
.cut-end
# Right stem
.cut-begin roman-v
serif-entry none
depth 30
cut-path 350,720 ⏤ 650,0
serif-exit triangular 50 25
.cut-end
# Crossbar
.cut-begin roman-v
depth 20
cut-path 150,280 ⏤ 550,280
.cut-end
.glyph-end
Carved letters are designed to be read by shadow. The depth and angle of cuts determine how light falls:
use chisel_path::{LightSource, render_shadow};
let light = LightSource::new()
.angle(45) // 45° from top-left (classic)
.intensity(1.0);
// Render produces shadow map showing how letter appears
let shadow_map = render_shadow(&glyph, &light);Understanding serif types through their carving origin:
| Serif Type | Carving Technique | Examples |
|---|---|---|
| Triangular | Three cuts meeting at point | Trajan, Times |
| Bracketed | Curved transition cuts | Garamond, Palatino |
| Slab | Flat termination cuts | Rockwell, Clarendon |
| Wedge | Angled entry cut | Optima (subtle) |
| None | Clean stop (modern) | Futura, Helvetica |
Two fundamental carving approaches:
use chisel_path::CarvingStyle;
// Incised: Letters cut INTO stone (most inscriptions)
let incised = CarvingStyle::Incised;
// Relief: Background cut away, letters RAISED
let relief = CarvingStyle::Relief {
background_depth: 50,
};This crate enables modeling historically accurate Latin letterforms:
- Roman Square Capitals (1st-2nd century) — Trajan's Column
- Rustic Capitals — faster, more informal carving
- Uncial — rounded forms, Christian inscriptions
- Renaissance Inscriptional — Alberti, Palatino revival
chisel— Chisel tool definitions and profilesstone— Stone/material propertiesdynamics— Cut depth, angle, grain interactionserif— Serif cut types and techniquesstroke— Cut segments and stroke buildinglight— Shadow rendering for carved formsglyph— Complete glyph construction
Apache-2.0
Chisel-Path is a component of the 「」 Lingenic Compose typesetting system.