Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Morx #85

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open

Morx #85

wants to merge 18 commits into from

Conversation

cliu2018
Copy link
Collaborator

No description provided.

Copy link
Contributor

@wezm wezm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see this coming in. Hopefully it opens up shaping of fonts like Apple Color Emoji, Hoefler Text, Zapfino, etc.

Some general comments:

We have in the past been a bit stingy with derives on structs (see #69) it would be good to add Copy/Clone, and PartialEq/Eq to new public structs.

Speaking of public structs, we should consider making the fields of structs that map directly to table data public too.

I think we need some more test coverage of this code to ensure that it's working as expected and remains working in the future. It might be worth trying to see if there are any freely licensed fonts out there that have a morx table that we might be able to add to the repo (in tests/fonts) and write some tests to exercise the code using that font. Probably worth checking if Harfbuzz has already identified such a font.

The unicode text rendering tests also have 40 odd morx tests and we've already integrated Allosorts into that project. It would be worth running that test suite against this branch to see how it fares.

src/morx.rs Outdated
#[derive(Debug)]
pub struct MorxHeader {
version: u16,
unused: u16,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is unused I don't think we need to store it.

src/morx.rs Outdated

let features =
ctxt.read_array::<Feature>(usize::try_from(chain_header.n_feature_entries)?)?;
let feature_vec = features.to_vec();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to allocate a vector here, I think the features can be stored as ReadArray on Chain.

src/morx.rs Outdated
let subtable_header = ctxt.read::<SubtableHeader>()?;

let subtable_body: SubtableType;
let subtable_body_length: usize = usize::try_from(subtable_header.length - 12)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to annotate the type here and it might be worth including a comment to explain that 12 is subtracted to skip the size of the header fields.

src/morx.rs Outdated
//read the subtable to a Vec<u8> if it is another type other than ligature or noncontextual
//let subtable_array = ctxt.read_array::<U8>(subtable_body_length)?;
let subtable_array = ctxt.read_array::<U8>(subtable_body_length)?;
let subtable_vec = subtable_array.to_vec();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a vector needs to be allocated here. A slice could be used instead:

let subtable_scope = ctxt.read_scope(subtable_body_length)?;
let data = subtable_scope.data();

If this was done all branches would create the subtable_scope so that could be moved out of the match as well.

src/morx.rs Outdated
Comment on lines 224 to 245
/***************************************************************************************************
//--------------------------------------------------------------------------------------------------
#[derive(Debug)]
pub enum SubtableType {
Rearragement {
rearrangement_subtable: RearrangementSubtable,
},
Contextual {
contextual_subtable: ContextualSubstitutionSubtable,
},
Ligature {
ligature_subtable: LigatureSubtable,
},
NonContextual {
noncontextual_subtable: NonContextualSubstitutionSubtable,
},
Insertion {
insertion_subtable: InsertionSubtable,
},
}

****************************************************************************************************/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest deleting commented out code.

src/morx.rs Outdated

'glyph: loop {
let index_to_entry_table = contextual_subtable.state_array.state_array
[usize::from(self.next_state)][usize::from(class)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In places where we index into arrays with values provided by the font I think get should be used in order to avoid panics if the value happens to be out of bounds.

src/morx.rs Outdated
Comment on lines 1400 to 1408
'glyphs: loop {
let glyph: RawGlyph<()>;
if i < self.glyphs.len() {
glyph = self.glyphs[i].clone(); //length of glyphs might have changed due to substitution. So need to check.
}
//assume the length of the glyphs array is shorter or unchanged.
else {
break 'glyphs;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be a while loop:

while let Some(glyph) = self.glyphs.get(i) {
    let glyph = glyph.clone();}

{
contextual_subst.next_state = 0;
contextual_subst.process_glyphs(contextual_subtable)?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be else branches on these to return an error if the sub-table type is not the expected type?

Comment on lines +1606 to +1512
liga_subst.next_state = 0;
liga_subst.component_stack.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these lines duplicate what LigatureSubstitution::new does.

for entry in chain.feature_array.iter() {
match features {
Features::Custom(_features_list) => {
return Ok(subfeature_flags);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return the supplied custom features instead of the default features?

@cliu2018
Copy link
Collaborator Author

cliu2018 commented Jan 19, 2023 via email

Copy link
Contributor

@wezm wezm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is shaping up well. I've left a lot of comments—sorry. Many relate to style, following Rust idioms, or consistency with the rest of the code base. All comments prefixed with "Minor" or "Optional" are changes that I don't think should block merging this work, the others I think should be addressed before merging.

There are a couple general comments that apply in several places:

  • I think indexing, particularly when processing glyphs needs to reviewed to ensure it will not panic with user controlled data.
  • I think there are various places that probably should be using checked arithmetic. I noted some of them but not all.
  • There are some areas that are long and deeply nested which makes it a little difficult to read. I think extracting functions/adding methods to types could help but it's not essential.
  • It would be good to add at least one small (file size wise), permissively licensed font in the code base (E.g. tests/fonts/morx) and add some basic tests that use it to help ensure the code works on a real font (and stays working).

src/font.rs Show resolved Hide resolved
src/font.rs Show resolved Hide resolved
src/font.rs Show resolved Hide resolved
src/font.rs Show resolved Hide resolved

#[derive(Debug)]
pub struct MorxTable<'a> {
_morx_header: MorxHeader,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this field is unused I think it can be dropped from the struct.

}
}

// println!("The glyphs array after ligature substitutions: {:?}", glyphs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test should have assertions that the glyphs array is in the expected state after applying the substitutions.

}

//------------------------------------ Ligature Substitution Test ----------------------------------------------------------
pub fn morx_ligature_test<'a>(scope: ReadScope<'a>) -> Result<(), ParseError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should have test attribute so they are run by cargo test,

Suggested change
pub fn morx_ligature_test<'a>(scope: ReadScope<'a>) -> Result<(), ParseError> {
#[test]
pub fn morx_ligature_test<'a>(scope: ReadScope<'a>) -> Result<(), ParseError> {

Ok(subfeature_flags)
}

//------------------------------------ Ligature Substitution Test ----------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should be in a module guarded by the test attribute so they are only built for the test harness:

#[cfg(test)]
mod tests {

// test functions in here

}

Comment on lines +1663 to +1679
//string: "ptgffigpfl" (for Zapfino.ttf)
//let mut glyphs: Vec<u16> = vec![585, 604, 541, 536, 536, 552, 541, 585, 536, 565];

//string: "ptpfgffigpfl" (for Zapfino.ttf)
//let mut glyphs: Vec<u16> = vec![585, 604, 585, 536, 541, 536, 536, 552, 541, 585, 536, 565];

//string: "Zapfino" (for Zapfino.ttf)
//let mut glyphs: Vec<u16> = vec![104, 504, 585, 536, 552, 573, 580];

//string: "ptgffigpfl" (for Ayuthaya.ttf)
//let mut glyphs: Vec<u16> = vec![197, 201, 188, 187, 187, 190, 188, 197, 187, 193];

//string: ""\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F467}"" (for emoji.ttf)
//let mut glyphs: Vec<u16> = vec![1062, 43, 1164, 43, 1056, 43, 1056];

//string: "U+1F1E6 U+1F1FA" (for emoji.ttf)
//let mut glyphs: Vec<u16> = vec![16, 36];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented out code.

Comment on lines +1816 to +1819
//print glyph array after applying substitutions.
for glyph in glyphs.iter() {
println!(" {:?}", glyph);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should also be replaced by assertions that the glyphs are as expected.

@cliu2018
Copy link
Collaborator Author

cliu2018 commented Nov 11, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants