Skip to content

Commit f55559a

Browse files
committed
chore: protoype ligatures with ttf-parser
1 parent ac302b1 commit f55559a

File tree

5 files changed

+156
-2
lines changed

5 files changed

+156
-2
lines changed

Cargo.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ serde = "1.0"
2626
futures-lite = "2.3.0"
2727

2828
[features]
29-
ttf = ["dep:fontdue", "dep:tempfile"]
29+
ttf = ["dep:fontdue", "dep:tempfile", "dep:ttf-parser"]
3030
default = ["ttf"]
3131

3232
[dependencies.image]
@@ -38,6 +38,10 @@ features = ["png"]
3838
version = "0.9"
3939
optional = true
4040

41+
[dependencies.ttf-parser]
42+
version = "0.25"
43+
optional = true
44+
4145
[dependencies.tempfile]
4246
version = "3.17.1"
4347
optional = true

src/bin/ttf-info.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::path::PathBuf;
2+
3+
use clap::Parser;
4+
use color_eyre::eyre::{Context, ContextCompat, OptionExt};
5+
use image::{GrayImage, ImageFormat};
6+
use sdo_tool::ttf::{glyph_index_vec, LigatureInfo};
7+
use signum::chsets::{
8+
metrics::{FontMetrics, DEFAULT_FONT_SIZE},
9+
printer::PrinterKind,
10+
};
11+
use ttf_parser::{Face, GlyphId};
12+
13+
#[derive(Parser)]
14+
/// Options for decoding an ATARI String
15+
pub struct Opts {
16+
/// The file to convert
17+
font_file: PathBuf,
18+
19+
/// The ligature to check
20+
#[clap(default_value = "ch")]
21+
ligature: String,
22+
23+
#[clap(short, long, default_value = "0")]
24+
index: u32,
25+
}
26+
27+
fn main() -> color_eyre::Result<()> {
28+
color_eyre::install()?;
29+
let opt: Opts = Opts::parse();
30+
let data = std::fs::read(&opt.font_file)?;
31+
32+
let face = Face::parse(&data, opt.index)?;
33+
let l = LigatureInfo::new(&face);
34+
35+
let lig = opt.ligature.chars().collect::<Vec<_>>();
36+
37+
let glyphs = glyph_index_vec(&face, &lig).ok_or_eyre("Not all glyphs in font")?;
38+
let lig_glyph = l.find(&glyphs);
39+
println!("{:?} => {:?}", lig, lig_glyph);
40+
41+
let font = fontdue::Font::from_bytes(
42+
&data[..],
43+
fontdue::FontSettings {
44+
collection_index: opt.index,
45+
scale: 40.0,
46+
load_substitutions: true,
47+
},
48+
)
49+
.expect("already ttf parsed");
50+
if let Some(gl) = lig_glyph {
51+
let img = _raster(&font, gl)?;
52+
_show(&img)?;
53+
}
54+
55+
Ok(())
56+
}
57+
58+
fn _raster(font: &fontdue::Font, g: GlyphId) -> color_eyre::Result<GrayImage> {
59+
let font_size = DEFAULT_FONT_SIZE;
60+
let pk = PrinterKind::Needle24;
61+
let fm = FontMetrics::new(pk, font_size);
62+
let px_per_em = fm.em_square_pixels();
63+
64+
let (metrics, bitmap) = font.rasterize_indexed(g.0, px_per_em as f32);
65+
let inverted = bitmap.iter().copied().map(|c| 255 - c).collect();
66+
let img = GrayImage::from_vec(metrics.width as u32, metrics.height as u32, inverted)
67+
.context("image creation")?;
68+
Ok(img)
69+
}
70+
71+
fn _show(img: &GrayImage) -> color_eyre::Result<()> {
72+
let mut tmpfile =
73+
tempfile::NamedTempFile::with_suffix(".png").context("failed to create tempfile")?;
74+
img.write_to(&mut tmpfile, ImageFormat::Png)
75+
.expect("image to PDF");
76+
77+
let mut child = std::process::Command::new("eog")
78+
.arg(tmpfile.path())
79+
.spawn()?;
80+
81+
let _o = child.wait()?;
82+
Ok(())
83+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
pub mod cli;
2+
#[cfg(feature = "ttf")]
3+
pub mod ttf;

src/ttf.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use ttf_parser::{
2+
gsub::{LigatureSubstitution, SubstitutionSubtable},
3+
Face, GlyphId,
4+
};
5+
6+
pub struct LigatureInfo<'a> {
7+
ligature_substitutions: Vec<LigatureSubstitution<'a>>,
8+
}
9+
10+
impl<'a> LigatureInfo<'a> {
11+
pub fn new(face: &Face<'a>) -> Self {
12+
Self {
13+
ligature_substitutions: face
14+
.tables()
15+
.gsub
16+
.map(|gsub| {
17+
gsub.lookups
18+
.into_iter()
19+
.flat_map(|lookup| {
20+
lookup
21+
.subtables
22+
.into_iter::<SubstitutionSubtable>()
23+
.filter_map(|subtable| match subtable {
24+
SubstitutionSubtable::Ligature(lig) => Some(lig),
25+
_ => None,
26+
})
27+
})
28+
.collect()
29+
})
30+
.unwrap_or_default(),
31+
}
32+
}
33+
34+
pub fn find(&self, glyphs: &[GlyphId]) -> Option<GlyphId> {
35+
let (&first, rest) = glyphs.split_first()?;
36+
37+
for lig in &self.ligature_substitutions {
38+
if let Some(coverage) = lig.coverage.get(first) {
39+
let l = lig.ligature_sets.get(coverage)?;
40+
for ligature in l {
41+
if ligature.components.into_iter().eq(rest.iter().copied()) {
42+
return Some(ligature.glyph);
43+
}
44+
}
45+
return None;
46+
}
47+
}
48+
None
49+
}
50+
}
51+
52+
pub fn glyph_index_vec(face: &Face<'_>, lig: &[char]) -> Option<Vec<GlyphId>> {
53+
let mut v = Vec::new();
54+
for &code_point in lig {
55+
v.push(face.glyph_index(code_point)?);
56+
}
57+
Some(v)
58+
}

0 commit comments

Comments
 (0)