profile
viewpoint
Evgeniy Reizner RazrFalcon Ukraine

edent/SuperTinyIcons 8987

Under 1KB each! Super Tiny Icons are miniscule SVG versions of your favourite website and app logos

harfbuzz/harfbuzz 1682

HarfBuzz text shaping engine

RazrFalcon/svgcleaner 1205

svgcleaner could help you to clean up your SVG files from the unnecessary data.

RazrFalcon/cargo-bloat 906

Find out what takes most of the space in your executable.

RazrFalcon/resvg 797

An SVG rendering library.

RazrFalcon/svgcleaner-gui 195

GUI for svgcleaner.

RazrFalcon/pico-args 185

An ultra simple CLI arguments parser.

RazrFalcon/roxmltree 139

Represent an XML 1.0 document as a read-only tree.

RazrFalcon/rustybuzz 60

An incremental harfbuzz port to Rust

RazrFalcon/choose-your-xml-rs 55

(DEPRECATED) Choose your XML library in Rust

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 9332c6ecc27eeefe7a45e529419bc934cee0ba03

Use stable wide crate.

view details

Evgeniy Reizner

commit sha ab986cc733a6ef70ca09b3ae89a60877af1f7f0f

Reduce public API. We can provide a more advance API later.

view details

Evgeniy Reizner

commit sha 5f54d7a1b878722e556c2d63c8a3820a54aad4f7

Allow fallback to SolidColor shader during gradient creation.

view details

push time in 8 hours

pull request commentRazrFalcon/pico-args

allow negative numbers in free opts

Thanks.

slyshykO

comment created time in a day

push eventRazrFalcon/pico-args

Oleksiy Slyshyk

commit sha cfc40e4766f8b492df7da7a223f0568bb999c6db

Allow negative numbers in free args.

view details

push time in a day

Pull request review commentRazrFalcon/pico-args

allow negative numbers in free opts

 impl Arguments {         let mut flags_left = Vec::new();         for arg in &self.0 {             if let Some(s) = arg.to_str() {-                if s.starts_with('-') && s != "-" {+                if s.starts_with('-') && s != "-" && !s.chars().nth(1).unwrap_or('f').is_numeric() {

You can replace this with s.chars().skip(1).all(|c| c.is_ascii_digit() || c == '.'))

slyshykO

comment created time in a day

PullRequestReviewEvent
PullRequestReviewEvent

pull request commentRazrFalcon/pico-args

allow negative numbers in free opts

This is indeed an interesting edge case.

Could you keep the original formatting and also add some tests? Thanks.

slyshykO

comment created time in a day

pull request commentLokathor/wide

Lower i32x8/u32x8 SSE requirements from SSSE3 down to SSE2.

Thanks!

RazrFalcon

comment created time in a day

issue closedRazrFalcon/xmlparser

Can I parse HTML5 with this crate?

I've successfully used xmlparser to parse HTML, however I noticed that xmlparser does not accept attributes without quotes (and then some other things). Is this project willing to accept patches that are at least avoiding errors on html5 or is this entirely out of scope?

closed time in a day

untitaker

issue commentRazrFalcon/xmlparser

Can I parse HTML5 with this crate?

No. And this is not planed.

AFAIK, HTML5 is far more complex than simply omitting quotes around attribute values. It would be easier to simply fork the xmlparser and add an HTML specific stuff.

untitaker

comment created time in a day

issue commentRazrFalcon/tiny-skia

Linear blending?

Nice!

virtualritz

comment created time in 2 days

pull request commentLokathor/wide

Lower i32x8/u32x8 SSE requirements from SSSE3 down to SSE2.

I'm not in a hurry. Don't worry. Just a reminder.

RazrFalcon

comment created time in 3 days

pull request commentLokathor/wide

Lower i32x8/u32x8 SSE requirements from SSSE3 down to SSE2.

Version bump?

RazrFalcon

comment created time in 3 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha eeb25729db0fd440a6606c9c722ae929b0560b95

Rename path_ops into path64.

view details

Evgeniy Reizner

commit sha 0af8e801f8e4c14e165b97d06ac9a413c888d4a1

Add stroke dashing support.

view details

push time in 3 days

issue commentredox-os/orbtk

Consider using tiny-skia

Glad to here. After 3 weeks, I'm still at 90%. This how software development works. =)

The library become 20-50% faster and there are only two things left: stroke dashing (easy) and clipping (hard). Once again, I hope to finish in two weeks. But we'll see.

From the performance prospective, tiny-skia is 2-3x faster than cairo in most cases, but there are still weird cases when cairo is faster. And compared to raqote, we are 4-7x faster, depending on a task. Also, unlike raqote, tiny-skia supports bicubic bitmaps transformation and hairline stroking (stroke-width < 1px).

RazrFalcon

comment created time in 5 days

issue commentRazrFalcon/resvg

Incorrect edge using gaussian blur

This is a know bug and it's pretty hard to fix.

wangrl2016

comment created time in 5 days

issue closedRazrFalcon/resvg

Incorrect edge using gaussian blur

The zip has a svg file, the background image using guassian blur, but the edge show white line.

gaussian_blur_svg.zip

sceenshot

closed time in 5 days

wangrl2016

issue commentRazrFalcon/resvg

Incorrect edge using gaussian blur

Duplicate of #241

wangrl2016

comment created time in 5 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 837dbd2e4680e649f60cf5ae6bc7ca7e0bf441d4

Tiny refactoring.

view details

Evgeniy Reizner

commit sha b8cb6dc30edbea0f5e9cc5b86f6edc22c4b27333

Update benchmark results. I forgot to update SSE2 results after enabling AVX.

view details

push time in 7 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha a4148f9af94166c6717dd38c6111062b98eb1eeb

Reduce unsafe. Document all unsafe blocks and document how the raster pipeline works.

view details

push time in 8 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha db1c13a1928930255a6fc1e2dcfd463469aa3ac4

Small refactoring.

view details

Evgeniy Reizner

commit sha 37d21eed6bf516ed25dc064b837bc667cdbbdd9c

10-15% faster. Looks like passing by reference isn't as cheap as I thought. At least in u16x16 case.

view details

Evgeniy Reizner

commit sha a5737da49fd07d28870cc178079ef63535a9e164

Enable AVX support. Up to 2x faster is some cases.

view details

push time in 8 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

Sure. Not problem.

laurmaedje

comment created time in 9 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

By the way, I have a separate utility project called ttf-explorer, which can help you understand the binary structure more easily. The problem is that TrueType tables that it supports, are the same as supported by ttf-parser. So no GSUB/GPOS.

I'm actually adding a new table to ttf-explorer first and only then implementing it in ttf-parser. Maybe because this way you can actually see how the data is laid out.

laurmaedje

comment created time in 9 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

You're talking about fuzzing. We don't really need it for now. As long as rb passes all hb tests - I'm fine.

Yes, GSUB/GPOS are obviously more relevant and have a higher priority. Especially for cases when people bundle app fonts.

If you have a free time to work on this project, I would suggest trying GSUB/GPOS. Forget about AAT. We can move it to a separate C++ crate as an optional dependency.

ttf-parser/next has some initial GSUB/GPOS support, so you can start from there. But I strongly suggest implementing it on the rb side first. So there will be no need to jump between projects and designing an API. We can transfer it back to ttf-parser later.

harfbuzz is actively developed

Not really, actually. Most of the changes are subsetting related, which we don't have. Afaik, there were no radical changes in shaping since variable fonts (which were added two years ago or so). So don't worry about that.

I would say, that it possible to finish rb in 1-2 month without AAT. And then we can think about improving tests coverage and stuff.

laurmaedje

comment created time in 9 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

@laurmaedje There is no point in testing using wiki, since the problem is that specific font tables are available only in proprietary fonts.

As for the optional AAT, yes, this is one possibility. But on the other hand, you have to jump straight into GSUB/GPOS, which are no less complex. I would say it would take me at least a month to implement those tables. Maybe more.

laurmaedje

comment created time in 9 days

push eventRazrFalcon/rustybuzz

Laurenz

commit sha cf6ce5783d4d3d355928ff1a82a04648b02e368b

Port normalization.

view details

push time in 9 days

PR merged RazrFalcon/rustybuzz

Port normalization.

Okay, I ported the normalization. This one was already a bit trickier because it was more intertwined, especially with the compose/decompose function pointers through FFI.

Some observations:

  • The existing complex shaper's compose/decompose functions used Rust's bool through FFI, which I think was incorrect. I changed it to rb_bool_t.
  • I had to rename _rb_glyph_info_set_unicode_props to init_unicode_props in Rust because it would have clashed with the existing set_unicode_props. Furthermore, the logic is duplicated in Rust and C because there wasn't any GlyphInfo FFI so far and I didn't think it was worth it.
  • I also had to do a mimimal port of rb_ot_complex_shaper_t to get some properties needed for normalization.

I think, maybe the main shaping logic could be ported next, but I have to look more closely whether there are still any obstacles that need to be taken care of before.

+834 -585

10 comments

21 changed files

laurmaedje

pr closed time in 9 days

PullRequestReviewEvent

pull request commentRazrFalcon/rustybuzz

Port normalization.

the actual code is in the ot*.py files

Can you provide a link to it?

but these are not the only morx tests

I was talking about Apple fonts in general.

laurmaedje

comment created time in 10 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

  1. _m_o_r_t.py and _m_o_r_x.py are empty.
  2. I was talking about this and this.
laurmaedje

comment created time in 10 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

FontTools support reading and writing them

I haven't found kerx/morx code in fonttools. Am I missing something?

that is why macOS fonts are also tested

The current way of testing macOS font in harfbuzz drives me crazy. I understand that there is no easier way, but I don't want such monstrosity in rustybuzz.

laurmaedje

comment created time in 10 days

issue commentRazrFalcon/ttf-parser

Point direction request

So FT_Outline_Get_Orientation is very simple. It tires to fill a path and accumulates the final area. That it. So I guess you can implement this on your side. Since in case of ttf-parser we would have to outline the glyph twice or track the area.

Once again, I don't see a bug here. It's a tricky feature with no clear solution. And as far a I can tell, transformations has nothing to with this. You can make such malformed font even without transformations.

Also, looks like it's not glyf specific. CFF also affected.

mooman219

comment created time in 10 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

@laurmaedje To get a bit deeper into the Apple shaping testing, this is what we (someone) should do to make a proper implementation:

  1. Figure out how Apple's state machine generation is done. Because this is how kern, kerx, mort, morx are implemented. Afaik, there is no opensource implementation or even documentation. This task alone would take 2-3 months, or even more.
  2. Implement those tables in fonttools. This will also take 1-2 months.
  3. Write custom fonts from scratch. This would take few weeks.
  4. Replace harfbuzz's tests with the new one.
  5. Sync rustybuzz with harfbuzz.

As you can see, it could easily take half a year or even more. Which is an absurd amount of work. And basically the main reason why rustybuzz development become stale. I've already spend half a year on ttf-parser + rustybuzz and I simply could now work on it anymore. Turns out, that testing is times more complicated task than writing a TrueType parser and porting C++ to Rust.

Yes, we can simply port Apple table as is, without much testing, mainly because it's kinda an edge case and you have to try really hard to trigger it. Basically, you have to use specific Arabic fonts on macos. It would not affect any other OSes, really. But what's the point of RB then?

laurmaedje

comment created time in 10 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

@laurmaedje I've pushed the kerx branch. There are a lot of code and it even compiles. But it's not finished, tested or even "connected" to the harfbuzz. So I guess this is a good next step for you, since it still way easier than GSUB/GPOS.

As for the code/architecture: kern and kerx are implemented on the rb side (ignore ttf-parser kern implementation). Both parsing and processing. Eventually, we might move them to ttf-parser. But this is not a priority for now. As for testing, you have to test it on macos and find (somewhere) old Apple fonts. HB tests Apple fonts based on their hash, which is absurd, but there is no other legal way. That's why I've said in the readme that Apple shaping is an ultra-complex task. You basically have to write macos tests manually for RB based on macos.tests and then provide necessary files. No CI testing, obviously. Afaik, there is no way we can generate kerx, mort, morx tables ourselves, therefore we have to rely on existing Apple fonts. And obviously, we cannot simply ignore the Apple shaping, because it would break macos support.

And then we have to port mort/morx, which are very similar to kerx. This would finish Apple shaping. Then GSUB/GPOS and we done. Just mere 2-3 months of work... Yay!

Feel free to ask any questions.

laurmaedje

comment created time in 11 days

create barnchRazrFalcon/rustybuzz

branch : kerx

created branch time in 11 days

push eventRazrFalcon/ttf-parser

Evgeniy Reizner

commit sha 4a972d70b597b39b122f6a4d41c51e071c49912f

Allow 'true' magic.

view details

Evgeniy Reizner

commit sha b3d2b05769080bf7d0d4069abde6d0d05ad6fd8c

Added Face::variation_coordinates.

view details

Evgeniy Reizner

commit sha 9a71782f4cf5f72384fa22952b9735b9a78e4ae7

Face::glyph_name can lookup CFF names too.

view details

Zimon Dai

commit sha 91234fc92d82600ecb94b38b4341eac88b62ace8

Fix font width parse bug

view details

Evgeniy Reizner

commit sha bdcae55c8a48674c8db07cd15aff1391ef738987

Prefer explicit types to type inference during parsing. Type inference is very error prone in this case.

view details

Evgeniy Reizner

commit sha ce17a04e847998296e79edbfe8e8e1baf22b1445

Merge branch 'master' into next

view details

Evgeniy Reizner

commit sha da829e6251461b1f1aaa39eebf47fc501b67be97

Added Face::table_data.

view details

Evgeniy Reizner

commit sha 06a1f6dfd7fb42efa64ed97e8d33b63af2ecfe6f

Fixed possible u32 overflow on 32-bit platforms during Face::from_slice.

view details

Evgeniy Reizner

commit sha fe1dc388ecc654bb4fbddef822129aaa3f61615e

Added an iterator over cmap subtables.

view details

Evgeniy Reizner

commit sha 8db5ae4eb1b7cd66768bc524858d1ce9d8739aaf

Version bump.

view details

Laurenz

commit sha d7b04341f01747c60cf8a828f18551d621b56901

Extract more metrics from Post and OS/2 table - Expose capital height and typo metrics from OS/2 - Expose monospaced-ness and italic angle from post (closes #17)

view details

Evgeniy Reizner

commit sha 9054e2dd77cdce664a4d125469d10bd846e7d5b9

Version bump.

view details

Evgeniy Reizner

commit sha c5d3cfe7b6ee3a5d06a3f864f01d842ae9f2b768

Update readme.

view details

Laurenz

commit sha 0e3abf43adb91800c3a2f036d9945d9043d130e8

(cmap) Don't return glyph ID 0 instead of None for format 0.

view details

Laurenz

commit sha f6bb9bfa6ef8f49993646092beed6ec10230f582

(cmap) Fix invalid mapping for format 2.

view details

Laurenz

commit sha a0ec71758dba2c00a1f6557a4aa4cc70bfad5730

(cmap) Add cmap::Subtable::codepoints method. Closes #20

view details

Evgeniy Reizner

commit sha fe8b5780069a2e43f9e04f761b4e7cb9c88737e3

Small fixes.

view details

Evgeniy Reizner

commit sha c25f448d45913717e7e6fb8366500c04ea58d330

Version bump.

view details

Evgeniy Reizner

commit sha 30b427eebac0da13d2ed5b34c964c1ef2237adaa

Update readme.

view details

Evgeniy Reizner

commit sha 9f9ed7d021bcb1851d7f59356aa74da15f334b43

Merge branch 'master' into next

view details

push time in 11 days

pull request commentRazrFalcon/rustybuzz

Port normalization.

Everything is fine. I will try to find time to push kerx support soon, so you would have a complete access to all implemented features. And it would work as a template for other TrueType tables related code.

Also, update Readme and Changelog.

laurmaedje

comment created time in 11 days

Pull request review commentRazrFalcon/rustybuzz

Port normalization.

+use crate::{ffi, ot, Font};+use crate::buffer::{Buffer, BufferScratchFlags, GlyphInfo};+use crate::unicode::{CharExt, GeneralCategory};++// HIGHLEVEL DESIGN:+//+// This file exports one main function: _rb_ot_shape_normalize().+//+// This function closely reflects the Unicode Normalization Algorithm,+// yet it's different.+//+// Each shaper specifies whether it prefers decomposed (NFD) or composed (NFC).+// The logic however tries to use whatever the font can support.+//+// In general what happens is that: each grapheme is decomposed in a chain+// of 1:2 decompositions, marks reordered, and then recomposed if desired,+// so far it's like Unicode Normalization.  However, the decomposition and+// recomposition only happens if the font supports the resulting characters.+//+// The goals are:+//+//   - Try to render all canonically equivalent strings similarly.  To really+//     achieve this we have to always do the full decomposition and then+//     selectively recompose from there.  It's kinda too expensive though, so+//     we skip some cases.  For example, if composed is desired, we simply+//     don't touch 1-character clusters that are supported by the font, even+//     though their NFC may be different.+//+//   - When a font has a precomposed character for a sequence but the 'ccmp'+//     feature in the font is not adequate, use the precomposed character+//     which typically has better mark positioning.+//+//   - When a font does not support a combining mark, but supports it precomposed+//     with previous base, use that.  This needs the itemizer to have this+//     knowledge too.  We need to provide assistance to the itemizer.+//+//   - When a font does not support a character but supports its canonical+//     decomposition, well, use the decomposition.+//+//   - The complex shapers can customize the compose and decompose functions to+//     offload some of their requirements to the normalizer.  For example, the+//     Indic shaper may want to disallow recomposing of two matras.++pub struct ShapeNormalizeContext {+    pub plan: ot::ShapePlan,+    pub(crate) buffer: &'static mut Buffer,+    pub font: &'static Font<'static>,+    pub decompose: ffi::rb_ot_decompose_func_t,+    pub compose: ffi::rb_ot_compose_func_t,+}++impl ShapeNormalizeContext {+    #[inline]+    pub fn from_ptr(ctx: *const ffi::rb_ot_shape_normalize_context_t) -> &'static Self {+        unsafe { &*(ctx as *const Self) }+    }++    #[inline]+    pub fn as_ptr(&self) -> *const ffi::rb_ot_shape_normalize_context_t {+        self as *const _ as *const ffi::rb_ot_shape_normalize_context_t+    }++    #[inline]+    pub fn decompose(&self, ab: u32) -> Option<(u32, u32)> {+        let mut a = 0;+        let mut b = 0;++        unsafe {+            if (self.decompose)(self.as_ptr(), ab, &mut a, &mut b) != 0 {+                return Some((a, b));+            }+        }++        None+    }++    #[inline]+    pub fn compose(&self, a: u32, b: u32) -> Option<u32> {+        let mut ab = 0;++        unsafe {+            if (self.compose)(self.as_ptr(), a, b, &mut ab) != 0 {+                return Some(ab);+            }+        }++        None+    }+}++#[derive(Clone, Copy, Debug, Eq, PartialEq)]+#[allow(dead_code)]+pub enum ShapeNormalizationMode {+    None = 0,+    Decomposed,+    /// Never composes base-to-base.+    ComposedDiacritics,+    /// Always fully decomposes and then recompose back.+    ComposedDiacriticsNoShortCircuit,+    Auto,+}++impl Default for ShapeNormalizationMode {+    fn default() -> Self {+        Self::Auto+    }+}++#[no_mangle]+pub extern "C" fn _rb_ot_shape_normalize(

Could you wrap extern functions?

So it would be like this:

#[no_mangle]
pub extern "C" fn _rb_ot_shape_normalize(
    plan: *const ffi::rb_ot_shape_plan_t,
    buffer: *mut ffi::rb_buffer_t,
    font: *mut ffi::rb_font_t,
) {
    let plan = ot::ShapePlan::from_ptr(plan);
    let buffer = Buffer::from_ptr_mut(buffer);
    let font = Font::from_ptr(font);
    normalize(plan, buffer, font);
}

fn normalize(...) { /* actual code */ }
laurmaedje

comment created time in 11 days

Pull request review commentRazrFalcon/rustybuzz

Port normalization.

+use crate::{ffi, ot, Font};+use crate::buffer::{Buffer, BufferScratchFlags, GlyphInfo};+use crate::unicode::{CharExt, GeneralCategory};++// HIGHLEVEL DESIGN:+//+// This file exports one main function: _rb_ot_shape_normalize().+//+// This function closely reflects the Unicode Normalization Algorithm,+// yet it's different.+//+// Each shaper specifies whether it prefers decomposed (NFD) or composed (NFC).+// The logic however tries to use whatever the font can support.+//+// In general what happens is that: each grapheme is decomposed in a chain+// of 1:2 decompositions, marks reordered, and then recomposed if desired,+// so far it's like Unicode Normalization.  However, the decomposition and+// recomposition only happens if the font supports the resulting characters.+//+// The goals are:+//+//   - Try to render all canonically equivalent strings similarly.  To really+//     achieve this we have to always do the full decomposition and then+//     selectively recompose from there.  It's kinda too expensive though, so+//     we skip some cases.  For example, if composed is desired, we simply+//     don't touch 1-character clusters that are supported by the font, even+//     though their NFC may be different.+//+//   - When a font has a precomposed character for a sequence but the 'ccmp'+//     feature in the font is not adequate, use the precomposed character+//     which typically has better mark positioning.+//+//   - When a font does not support a combining mark, but supports it precomposed+//     with previous base, use that.  This needs the itemizer to have this+//     knowledge too.  We need to provide assistance to the itemizer.+//+//   - When a font does not support a character but supports its canonical+//     decomposition, well, use the decomposition.+//+//   - The complex shapers can customize the compose and decompose functions to+//     offload some of their requirements to the normalizer.  For example, the+//     Indic shaper may want to disallow recomposing of two matras.++pub struct ShapeNormalizeContext {+    pub plan: ot::ShapePlan,+    pub(crate) buffer: &'static mut Buffer,+    pub font: &'static Font<'static>,+    pub decompose: ffi::rb_ot_decompose_func_t,+    pub compose: ffi::rb_ot_compose_func_t,+}++impl ShapeNormalizeContext {+    #[inline]+    pub fn from_ptr(ctx: *const ffi::rb_ot_shape_normalize_context_t) -> &'static Self {+        unsafe { &*(ctx as *const Self) }+    }++    #[inline]+    pub fn as_ptr(&self) -> *const ffi::rb_ot_shape_normalize_context_t {+        self as *const _ as *const ffi::rb_ot_shape_normalize_context_t+    }++    #[inline]+    pub fn decompose(&self, ab: u32) -> Option<(u32, u32)> {+        let mut a = 0;+        let mut b = 0;++        unsafe {+            if (self.decompose)(self.as_ptr(), ab, &mut a, &mut b) != 0 {+                return Some((a, b));+            }+        }++        None+    }++    #[inline]+    pub fn compose(&self, a: u32, b: u32) -> Option<u32> {+        let mut ab = 0;++        unsafe {+            if (self.compose)(self.as_ptr(), a, b, &mut ab) != 0 {+                return Some(ab);+            }+        }++        None+    }+}++#[derive(Clone, Copy, Debug, Eq, PartialEq)]+#[allow(dead_code)]+pub enum ShapeNormalizationMode {+    None = 0,+    Decomposed,+    /// Never composes base-to-base.+    ComposedDiacritics,+    /// Always fully decomposes and then recompose back.+    ComposedDiacriticsNoShortCircuit,+    Auto,+}++impl Default for ShapeNormalizationMode {+    fn default() -> Self {+        Self::Auto+    }+}++#[no_mangle]+pub extern "C" fn _rb_ot_shape_normalize(+    plan: *const ffi::rb_ot_shape_plan_t,+    buffer: *mut ffi::rb_buffer_t,+    font: *mut ffi::rb_font_t,+) {+    let plan = ot::ShapePlan::from_ptr(plan);+    let buffer = Buffer::from_ptr_mut(buffer);+    let font = Font::from_ptr(font);++    if buffer.is_empty() {+        return;+    }++    let mut mode = plan.ot_shaper.normalization_preference();+    if mode == ShapeNormalizationMode::Auto {+        // https://github.com/harfbuzz/harfbuzz/issues/653#issuecomment-423905920+        // if plan.has_gpos_mark() {+        //     mode = ShapeNormalizationMode::Decomposed;+        // }+        mode = ShapeNormalizationMode::ComposedDiacritics;+    }++    let decompose = plan.ot_shaper.get_decompose().unwrap_or(decompose_unicode);+    let compose = plan.ot_shaper.get_compose().unwrap_or(compose_unicode);+    let mut ctx = ShapeNormalizeContext { plan, buffer, font, decompose, compose };+    let mut buffer = &mut ctx.buffer;++    let always_short_circuit = mode == ShapeNormalizationMode::None;+    let might_short_circuit = always_short_circuit || !matches!(+        mode,+        ShapeNormalizationMode::Decomposed |+        ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit+    );++    // We do a fairly straightforward yet custom normalization process in three+    // separate rounds: decompose, reorder, recompose (if desired).  Currently+    // this makes two buffer swaps.  We can make it faster by moving the last+    // two rounds into the inner loop for the first round, but it's more readable+    // this way.++    // First round, decompose+    let mut all_simple = true;+    {+        let count = buffer.len;+        buffer.idx = 0;+        buffer.clear_output();+        loop {+            let mut end = buffer.idx + 1;+            while end < count && !buffer.info[end].is_unicode_mark() {+                end += 1;+            }++            if end < count {+                // Leave one base for the marks to cluster with.+                end -= 1;+            }++            // From idx to end are simple clusters.+            if might_short_circuit {+                let len = end - buffer.idx;+                let mut done = 0;+                while done < len {+                    let cur = buffer.cur_mut(done);+                    *cur.glyph_index() = match font.glyph_index(cur.codepoint) {+                        Some(glyph_id) => glyph_id.0 as u32,+                        None => break,+                    };+                    done += 1;+                }+                buffer.next_glyphs(done);+            }++            while buffer.idx < end && buffer.successful {+                decompose_current_character(&mut ctx, might_short_circuit);+                buffer = &mut ctx.buffer;+            }++            if buffer.idx == count || !buffer.successful {+                break;+            }++            all_simple = false;++            // Find all the marks now.+            end = buffer.idx + 1;+            while end < count && buffer.info[end].is_unicode_mark() {+                end += 1;+            }++            // idx to end is one non-simple cluster.+            decompose_multi_char_cluster(&mut ctx, end, always_short_circuit);+            buffer = &mut ctx.buffer;++            if buffer.idx >= count || !buffer.successful {+                break;+            }+        }++        buffer.swap_buffers();+    }++    // Second round, reorder (inplace)+    if !all_simple {+        let count = buffer.len;+        let mut i = 0;+        while i < count {+            if buffer.info[i].modified_combining_class() == 0 {+                i += 1;+                continue;+            }++            let mut end = i + 1;+            while end < count && buffer.info[end].modified_combining_class() != 0 {+                end += 1;+            }++            // We are going to do a O(n^2).  Only do this if the sequence is short.+            if end - i <= ot::MAX_COMBINING_MARKS {+                buffer.sort(i, end, compare_combining_class);++                if let Some(reorder_marks) = ctx.plan.ot_shaper.get_reorder_marks() {+                    unsafe {+                        reorder_marks(ctx.plan.as_ptr(), buffer.as_ptr(), i as u32, end as u32);+                    }+                }+            }++            i = end + 1;+        }+    }+    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_CGJ) {+        // For all CGJ, check if it prevented any reordering at all.+        // If it did NOT, then make it skippable.+        // https://github.com/harfbuzz/harfbuzz/issues/554+        for i in 1..buffer.len.saturating_sub(1) {+            if buffer.info[i].codepoint == 0x034F /* CGJ */ {+                let last = buffer.info[i - 1].modified_combining_class();+                let next = buffer.info[i + 1].modified_combining_class();+                if next == 0 || last <= next {+                    buffer.info[i].unhide();+                }+            }+        }+    }++    // Third round, recompose+    if !all_simple && matches!(+        mode,+        ShapeNormalizationMode::ComposedDiacritics |+        ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit+    ) {+        // As noted in the comment earlier, we don't try to combine+        // ccc=0 chars with their previous Starter.++        let count = buffer.len;+        let mut starter = 0;+        buffer.clear_output();+        buffer.next_glyph();+        while buffer.idx < count && buffer.successful {+            // We don't try to compose a non-mark character with it's preceding starter.+            // This is both an optimization to avoid trying to compose every two neighboring+            // glyphs in most scripts AND a desired feature for Hangul.  Apparently Hangul+            // fonts are not designed to mix-and-match pre-composed syllables and Jamo.+            let cur = buffer.cur(0);+            if cur.is_unicode_mark() &&+                // If there's anything between the starter and this char, they should have CCC+                // smaller than this character's.+                (starter == buffer.out_len - 1+                    || buffer.prev().modified_combining_class() < cur.modified_combining_class())+            {+                let a = buffer.out_info()[starter].codepoint;+                let b = cur.codepoint;+                if let Some(composed) = ctx.compose(a, b) {+                    if let Some(glyph_id) = font.glyph_index(composed) {+                        // Copy to out-buffer.+                        buffer = &mut ctx.buffer;+                        buffer.next_glyph();+                        if !buffer.successful {+                            return;+                        }++                        // Merge and remove the second composable.+                        buffer.merge_out_clusters(starter, buffer.out_len);+                        buffer.out_len -= 1;++                        // Modify starter and carry on.+                        let mut flags = buffer.scratch_flags;+                        let mut info = &mut buffer.out_info_mut()[starter];+                        info.codepoint = composed;+                        *info.glyph_index() = glyph_id.0 as u32;+                        info.init_unicode_props(&mut flags);+                        buffer.scratch_flags = flags;++                        continue;+                    }+                }+            }++            // Blocked, or doesn't compose.+            buffer = &mut ctx.buffer;+            buffer.next_glyph();++            if buffer.prev().modified_combining_class() == 0 {+                starter = buffer.out_len - 1;+            }+        }++        buffer.swap_buffers();+    }+}++fn decompose_multi_char_cluster(ctx: &mut ShapeNormalizeContext, end: usize, short_circuit: bool) {+    let mut i = ctx.buffer.idx;+    while i < end && ctx.buffer.successful {+        if ctx.buffer.info[i].as_char().is_variation_selector() {+            handle_variation_selector_cluster(ctx, end, short_circuit);+            return;+        }+        i += 1;+    }++    while ctx.buffer.idx < end && ctx.buffer.successful {+        decompose_current_character(ctx, short_circuit);+    }+}++fn handle_variation_selector_cluster(ctx: &mut ShapeNormalizeContext, end: usize, _: bool) {+    // TODO: Currently if there's a variation-selector we give-up, it's just too hard.+    let buffer = &mut ctx.buffer;+    let font = ctx.font;+    while buffer.idx < end - 1 && buffer.successful {+        if buffer.cur(1).as_char().is_variation_selector() {+            if let Some(glyph_id) = font.glyph_variation_index(+                buffer.cur(0).as_char(),+                buffer.cur(1).as_char(),+            ) {+                *buffer.cur_mut(0).glyph_index() = glyph_id.0 as u32;+                let unicode = buffer.cur(0).codepoint;+                buffer.replace_glyphs(2, 1, &[unicode]);+            } else {+                // Just pass on the two characters separately, let GSUB do its magic.+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+            }++            // Skip any further variation selectors.+            while buffer.idx < end && buffer.cur(0).as_char().is_variation_selector() {+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+            }+        } else {+            set_glyph(buffer.cur_mut(0), font);+            buffer.next_glyph();+        }+    }++    if ctx.buffer.idx < end {+        set_glyph(ctx.buffer.cur_mut(0), font);+        ctx.buffer.next_glyph();+    }+}++fn decompose_current_character(ctx: &mut ShapeNormalizeContext, shortest: bool) {+    let u = ctx.buffer.cur(0).as_char();+    let glyph = ctx.font.glyph_index(u as u32);++    if !shortest || glyph.is_none() {+        if decompose(ctx, shortest, u as u32) > 0 {+            skip_char(ctx.buffer);+            return;+        }+    }++    if let Some(glyph) = glyph {+        next_char(ctx.buffer, glyph.0 as u32);+        return;+    }++    // Handle space characters.+    if ctx.buffer.cur(0).general_category() == GeneralCategory::SpaceSeparator {+        if let Some(space_type) = u.space_fallback() {+            if let Some(space_glyph) = ctx.font.glyph_index(' ' as u32) {+                ctx.buffer.cur_mut(0).set_space_fallback(space_type);+                next_char(ctx.buffer, space_glyph.0 as u32);+                ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_SPACE_FALLBACK;+                return;+            }+        }+    }++    // U+2011 is the only sensible character that is a no-break version of another character+    // and not a space.  The space ones are handled already.  Handle this lone one.+    if u as u32 == 0x2011 {+        if let Some(other_glyph) = ctx.font.glyph_index(0x2010) {+            next_char(ctx.buffer, other_glyph.0 as u32);+            return;+        }+    }++    // Insert a .notdef glyph if decomposition failed.+    next_char(ctx.buffer, 0);+}++/// Returns 0 if didn't decompose, number of resulting characters otherwise.+fn decompose(ctx: &mut ShapeNormalizeContext, shortest: bool, ab: u32) -> u32 {+    let (a, b) = match ctx.decompose(ab) {+        Some(decomposed) => decomposed,+        _ => return 0,+    };++    let a_glyph = ctx.font.glyph_index(a);+    let b_glyph = if b != 0 {+        match ctx.font.glyph_index(b) {+            Some(glyph_id) => Some(glyph_id),+            None => return 0,+        }+    } else {+        None+    };++    if !shortest || a_glyph.is_none() {+        let ret = decompose(ctx, shortest, a);+        if ret != 0 {+            if let Some(b_glyph) = b_glyph {+                output_char(ctx.buffer, b, b_glyph.0 as u32);+                return ret + 1;+            }+            return ret;+        }+    }++    if let Some(a_glyph) = a_glyph {+        // Output a and b.+        output_char(ctx.buffer, a, a_glyph.0 as u32);+        if let Some(b_glyph) = b_glyph {+            output_char(ctx.buffer, b, b_glyph.0 as u32);+            return 2;+        }+        return 1;+    }++    0+}++extern "C" fn decompose_unicode(+    _: *const ffi::rb_ot_shape_normalize_context_t,+    ab: ffi::rb_codepoint_t,+    a: *mut ffi::rb_codepoint_t,+    b: *mut ffi::rb_codepoint_t,+) -> ffi::rb_bool_t {+    crate::unicode::rb_ucd_decompose(ab, a, b)+}++extern "C" fn compose_unicode(+    _: *const ffi::rb_ot_shape_normalize_context_t,+    a: ffi::rb_codepoint_t,+    b: ffi::rb_codepoint_t,+    ab: *mut ffi::rb_codepoint_t,+) -> ffi::rb_bool_t {+    crate::unicode::rb_ucd_compose(a, b, ab)+}++fn compare_combining_class(pa: &GlyphInfo, pb: &GlyphInfo) -> bool {+    pa.modified_combining_class() > pb.modified_combining_class()+}++fn set_glyph(info: &mut GlyphInfo, font: &Font) {+    if let Some(glyph_id) = font.glyph_index(info.codepoint) {+        *info.glyph_index() = glyph_id.0 as u32;+    }+}++fn output_char(buffer: &mut Buffer, unichar: u32, glyph: u32) {

Let's make it as Buffer methods.

laurmaedje

comment created time in 11 days

Pull request review commentRazrFalcon/rustybuzz

Port normalization.

+use crate::{ffi, ot, Font};+use crate::buffer::{Buffer, BufferScratchFlags, GlyphInfo};+use crate::unicode::{CharExt, GeneralCategory};++// HIGHLEVEL DESIGN:+//+// This file exports one main function: _rb_ot_shape_normalize().+//+// This function closely reflects the Unicode Normalization Algorithm,+// yet it's different.+//+// Each shaper specifies whether it prefers decomposed (NFD) or composed (NFC).+// The logic however tries to use whatever the font can support.+//+// In general what happens is that: each grapheme is decomposed in a chain+// of 1:2 decompositions, marks reordered, and then recomposed if desired,+// so far it's like Unicode Normalization.  However, the decomposition and+// recomposition only happens if the font supports the resulting characters.+//+// The goals are:+//+//   - Try to render all canonically equivalent strings similarly.  To really+//     achieve this we have to always do the full decomposition and then+//     selectively recompose from there.  It's kinda too expensive though, so+//     we skip some cases.  For example, if composed is desired, we simply+//     don't touch 1-character clusters that are supported by the font, even+//     though their NFC may be different.+//+//   - When a font has a precomposed character for a sequence but the 'ccmp'+//     feature in the font is not adequate, use the precomposed character+//     which typically has better mark positioning.+//+//   - When a font does not support a combining mark, but supports it precomposed+//     with previous base, use that.  This needs the itemizer to have this+//     knowledge too.  We need to provide assistance to the itemizer.+//+//   - When a font does not support a character but supports its canonical+//     decomposition, well, use the decomposition.+//+//   - The complex shapers can customize the compose and decompose functions to+//     offload some of their requirements to the normalizer.  For example, the+//     Indic shaper may want to disallow recomposing of two matras.++pub struct ShapeNormalizeContext {+    pub plan: ot::ShapePlan,+    pub(crate) buffer: &'static mut Buffer,+    pub font: &'static Font<'static>,+    pub decompose: ffi::rb_ot_decompose_func_t,+    pub compose: ffi::rb_ot_compose_func_t,+}++impl ShapeNormalizeContext {+    #[inline]+    pub fn from_ptr(ctx: *const ffi::rb_ot_shape_normalize_context_t) -> &'static Self {+        unsafe { &*(ctx as *const Self) }+    }++    #[inline]+    pub fn as_ptr(&self) -> *const ffi::rb_ot_shape_normalize_context_t {+        self as *const _ as *const ffi::rb_ot_shape_normalize_context_t+    }++    #[inline]+    pub fn decompose(&self, ab: u32) -> Option<(u32, u32)> {+        let mut a = 0;+        let mut b = 0;++        unsafe {+            if (self.decompose)(self.as_ptr(), ab, &mut a, &mut b) != 0 {+                return Some((a, b));+            }+        }++        None+    }++    #[inline]+    pub fn compose(&self, a: u32, b: u32) -> Option<u32> {+        let mut ab = 0;++        unsafe {+            if (self.compose)(self.as_ptr(), a, b, &mut ab) != 0 {+                return Some(ab);+            }+        }++        None+    }+}++#[derive(Clone, Copy, Debug, Eq, PartialEq)]+#[allow(dead_code)]+pub enum ShapeNormalizationMode {+    None = 0,+    Decomposed,+    /// Never composes base-to-base.+    ComposedDiacritics,+    /// Always fully decomposes and then recompose back.+    ComposedDiacriticsNoShortCircuit,+    Auto,+}++impl Default for ShapeNormalizationMode {+    fn default() -> Self {+        Self::Auto+    }+}++#[no_mangle]+pub extern "C" fn _rb_ot_shape_normalize(+    plan: *const ffi::rb_ot_shape_plan_t,+    buffer: *mut ffi::rb_buffer_t,+    font: *mut ffi::rb_font_t,+) {+    let plan = ot::ShapePlan::from_ptr(plan);+    let buffer = Buffer::from_ptr_mut(buffer);+    let font = Font::from_ptr(font);++    if buffer.is_empty() {+        return;+    }++    let mut mode = plan.ot_shaper.normalization_preference();+    if mode == ShapeNormalizationMode::Auto {+        // https://github.com/harfbuzz/harfbuzz/issues/653#issuecomment-423905920+        // if plan.has_gpos_mark() {+        //     mode = ShapeNormalizationMode::Decomposed;+        // }+        mode = ShapeNormalizationMode::ComposedDiacritics;+    }++    let decompose = plan.ot_shaper.get_decompose().unwrap_or(decompose_unicode);+    let compose = plan.ot_shaper.get_compose().unwrap_or(compose_unicode);+    let mut ctx = ShapeNormalizeContext { plan, buffer, font, decompose, compose };+    let mut buffer = &mut ctx.buffer;++    let always_short_circuit = mode == ShapeNormalizationMode::None;+    let might_short_circuit = always_short_circuit || !matches!(+        mode,+        ShapeNormalizationMode::Decomposed |+        ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit+    );++    // We do a fairly straightforward yet custom normalization process in three+    // separate rounds: decompose, reorder, recompose (if desired).  Currently+    // this makes two buffer swaps.  We can make it faster by moving the last+    // two rounds into the inner loop for the first round, but it's more readable+    // this way.++    // First round, decompose+    let mut all_simple = true;+    {+        let count = buffer.len;+        buffer.idx = 0;+        buffer.clear_output();+        loop {+            let mut end = buffer.idx + 1;+            while end < count && !buffer.info[end].is_unicode_mark() {+                end += 1;+            }++            if end < count {+                // Leave one base for the marks to cluster with.+                end -= 1;+            }++            // From idx to end are simple clusters.+            if might_short_circuit {+                let len = end - buffer.idx;+                let mut done = 0;+                while done < len {+                    let cur = buffer.cur_mut(done);+                    *cur.glyph_index() = match font.glyph_index(cur.codepoint) {+                        Some(glyph_id) => glyph_id.0 as u32,+                        None => break,+                    };+                    done += 1;+                }+                buffer.next_glyphs(done);+            }++            while buffer.idx < end && buffer.successful {+                decompose_current_character(&mut ctx, might_short_circuit);+                buffer = &mut ctx.buffer;+            }++            if buffer.idx == count || !buffer.successful {+                break;+            }++            all_simple = false;++            // Find all the marks now.+            end = buffer.idx + 1;+            while end < count && buffer.info[end].is_unicode_mark() {+                end += 1;+            }++            // idx to end is one non-simple cluster.+            decompose_multi_char_cluster(&mut ctx, end, always_short_circuit);+            buffer = &mut ctx.buffer;++            if buffer.idx >= count || !buffer.successful {+                break;+            }+        }++        buffer.swap_buffers();+    }++    // Second round, reorder (inplace)+    if !all_simple {+        let count = buffer.len;+        let mut i = 0;+        while i < count {+            if buffer.info[i].modified_combining_class() == 0 {+                i += 1;+                continue;+            }++            let mut end = i + 1;+            while end < count && buffer.info[end].modified_combining_class() != 0 {+                end += 1;+            }++            // We are going to do a O(n^2).  Only do this if the sequence is short.+            if end - i <= ot::MAX_COMBINING_MARKS {+                buffer.sort(i, end, compare_combining_class);++                if let Some(reorder_marks) = ctx.plan.ot_shaper.get_reorder_marks() {+                    unsafe {+                        reorder_marks(ctx.plan.as_ptr(), buffer.as_ptr(), i as u32, end as u32);+                    }+                }+            }++            i = end + 1;+        }+    }+    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_CGJ) {+        // For all CGJ, check if it prevented any reordering at all.+        // If it did NOT, then make it skippable.+        // https://github.com/harfbuzz/harfbuzz/issues/554+        for i in 1..buffer.len.saturating_sub(1) {+            if buffer.info[i].codepoint == 0x034F /* CGJ */ {+                let last = buffer.info[i - 1].modified_combining_class();+                let next = buffer.info[i + 1].modified_combining_class();+                if next == 0 || last <= next {+                    buffer.info[i].unhide();+                }+            }+        }+    }++    // Third round, recompose+    if !all_simple && matches!(+        mode,+        ShapeNormalizationMode::ComposedDiacritics |+        ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit+    ) {+        // As noted in the comment earlier, we don't try to combine+        // ccc=0 chars with their previous Starter.++        let count = buffer.len;+        let mut starter = 0;+        buffer.clear_output();+        buffer.next_glyph();+        while buffer.idx < count && buffer.successful {+            // We don't try to compose a non-mark character with it's preceding starter.+            // This is both an optimization to avoid trying to compose every two neighboring+            // glyphs in most scripts AND a desired feature for Hangul.  Apparently Hangul+            // fonts are not designed to mix-and-match pre-composed syllables and Jamo.+            let cur = buffer.cur(0);+            if cur.is_unicode_mark() &&+                // If there's anything between the starter and this char, they should have CCC+                // smaller than this character's.+                (starter == buffer.out_len - 1+                    || buffer.prev().modified_combining_class() < cur.modified_combining_class())+            {+                let a = buffer.out_info()[starter].codepoint;+                let b = cur.codepoint;+                if let Some(composed) = ctx.compose(a, b) {+                    if let Some(glyph_id) = font.glyph_index(composed) {+                        // Copy to out-buffer.+                        buffer = &mut ctx.buffer;+                        buffer.next_glyph();+                        if !buffer.successful {+                            return;+                        }++                        // Merge and remove the second composable.+                        buffer.merge_out_clusters(starter, buffer.out_len);+                        buffer.out_len -= 1;++                        // Modify starter and carry on.+                        let mut flags = buffer.scratch_flags;+                        let mut info = &mut buffer.out_info_mut()[starter];+                        info.codepoint = composed;+                        *info.glyph_index() = glyph_id.0 as u32;+                        info.init_unicode_props(&mut flags);+                        buffer.scratch_flags = flags;++                        continue;+                    }+                }+            }++            // Blocked, or doesn't compose.+            buffer = &mut ctx.buffer;+            buffer.next_glyph();++            if buffer.prev().modified_combining_class() == 0 {+                starter = buffer.out_len - 1;+            }+        }++        buffer.swap_buffers();+    }+}++fn decompose_multi_char_cluster(ctx: &mut ShapeNormalizeContext, end: usize, short_circuit: bool) {+    let mut i = ctx.buffer.idx;+    while i < end && ctx.buffer.successful {+        if ctx.buffer.info[i].as_char().is_variation_selector() {+            handle_variation_selector_cluster(ctx, end, short_circuit);+            return;+        }+        i += 1;+    }++    while ctx.buffer.idx < end && ctx.buffer.successful {+        decompose_current_character(ctx, short_circuit);+    }+}++fn handle_variation_selector_cluster(ctx: &mut ShapeNormalizeContext, end: usize, _: bool) {+    // TODO: Currently if there's a variation-selector we give-up, it's just too hard.+    let buffer = &mut ctx.buffer;+    let font = ctx.font;+    while buffer.idx < end - 1 && buffer.successful {+        if buffer.cur(1).as_char().is_variation_selector() {+            if let Some(glyph_id) = font.glyph_variation_index(+                buffer.cur(0).as_char(),+                buffer.cur(1).as_char(),+            ) {+                *buffer.cur_mut(0).glyph_index() = glyph_id.0 as u32;+                let unicode = buffer.cur(0).codepoint;+                buffer.replace_glyphs(2, 1, &[unicode]);+            } else {+                // Just pass on the two characters separately, let GSUB do its magic.+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+            }++            // Skip any further variation selectors.+            while buffer.idx < end && buffer.cur(0).as_char().is_variation_selector() {+                set_glyph(buffer.cur_mut(0), font);+                buffer.next_glyph();+            }+        } else {+            set_glyph(buffer.cur_mut(0), font);+            buffer.next_glyph();+        }+    }++    if ctx.buffer.idx < end {+        set_glyph(ctx.buffer.cur_mut(0), font);+        ctx.buffer.next_glyph();+    }+}++fn decompose_current_character(ctx: &mut ShapeNormalizeContext, shortest: bool) {+    let u = ctx.buffer.cur(0).as_char();+    let glyph = ctx.font.glyph_index(u as u32);++    if !shortest || glyph.is_none() {+        if decompose(ctx, shortest, u as u32) > 0 {+            skip_char(ctx.buffer);+            return;+        }+    }++    if let Some(glyph) = glyph {+        next_char(ctx.buffer, glyph.0 as u32);+        return;+    }++    // Handle space characters.+    if ctx.buffer.cur(0).general_category() == GeneralCategory::SpaceSeparator {+        if let Some(space_type) = u.space_fallback() {+            if let Some(space_glyph) = ctx.font.glyph_index(' ' as u32) {+                ctx.buffer.cur_mut(0).set_space_fallback(space_type);+                next_char(ctx.buffer, space_glyph.0 as u32);+                ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_SPACE_FALLBACK;+                return;+            }+        }+    }++    // U+2011 is the only sensible character that is a no-break version of another character+    // and not a space.  The space ones are handled already.  Handle this lone one.+    if u as u32 == 0x2011 {+        if let Some(other_glyph) = ctx.font.glyph_index(0x2010) {+            next_char(ctx.buffer, other_glyph.0 as u32);+            return;+        }+    }++    // Insert a .notdef glyph if decomposition failed.+    next_char(ctx.buffer, 0);+}++/// Returns 0 if didn't decompose, number of resulting characters otherwise.+fn decompose(ctx: &mut ShapeNormalizeContext, shortest: bool, ab: u32) -> u32 {+    let (a, b) = match ctx.decompose(ab) {+        Some(decomposed) => decomposed,+        _ => return 0,+    };++    let a_glyph = ctx.font.glyph_index(a);+    let b_glyph = if b != 0 {+        match ctx.font.glyph_index(b) {+            Some(glyph_id) => Some(glyph_id),+            None => return 0,+        }+    } else {+        None+    };++    if !shortest || a_glyph.is_none() {+        let ret = decompose(ctx, shortest, a);+        if ret != 0 {+            if let Some(b_glyph) = b_glyph {+                output_char(ctx.buffer, b, b_glyph.0 as u32);+                return ret + 1;+            }+            return ret;+        }+    }++    if let Some(a_glyph) = a_glyph {+        // Output a and b.+        output_char(ctx.buffer, a, a_glyph.0 as u32);+        if let Some(b_glyph) = b_glyph {+            output_char(ctx.buffer, b, b_glyph.0 as u32);+            return 2;+        }+        return 1;+    }++    0+}++extern "C" fn decompose_unicode(+    _: *const ffi::rb_ot_shape_normalize_context_t,+    ab: ffi::rb_codepoint_t,+    a: *mut ffi::rb_codepoint_t,+    b: *mut ffi::rb_codepoint_t,+) -> ffi::rb_bool_t {+    crate::unicode::rb_ucd_decompose(ab, a, b)+}++extern "C" fn compose_unicode(+    _: *const ffi::rb_ot_shape_normalize_context_t,+    a: ffi::rb_codepoint_t,+    b: ffi::rb_codepoint_t,+    ab: *mut ffi::rb_codepoint_t,+) -> ffi::rb_bool_t {+    crate::unicode::rb_ucd_compose(a, b, ab)+}++fn compare_combining_class(pa: &GlyphInfo, pb: &GlyphInfo) -> bool {+    pa.modified_combining_class() > pb.modified_combining_class()+}++fn set_glyph(info: &mut GlyphInfo, font: &Font) {+    if let Some(glyph_id) = font.glyph_index(info.codepoint) {+        *info.glyph_index() = glyph_id.0 as u32;+    }+}++fn output_char(buffer: &mut Buffer, unichar: u32, glyph: u32) {+    *buffer.cur_mut(0).glyph_index() = glyph;+    // This is very confusing indeed.+    buffer.output_glyph(unichar);+    let mut flags = buffer.scratch_flags;+    buffer.prev_mut().init_unicode_props(&mut flags);+    buffer.scratch_flags = flags;+}++fn next_char(buffer: &mut Buffer, glyph: u32) {+    *buffer.cur_mut(0).glyph_index() = glyph;+    buffer.next_glyph();+}++fn skip_char(buffer: &mut Buffer) {

I think we can replace skip_char with skip_glyph altogether.

laurmaedje

comment created time in 11 days

PullRequestReviewEvent
PullRequestReviewEvent

issue commentRazrFalcon/ttf-parser

Point direction request

Ok. My point was that there is no bug, if all other libraries are doing the same. Which is still true. I consider this a feature, not a bug. Because the current code is correct. You just want information, which is unnecessary in many cases. Aka optional.

mooman219

comment created time in 11 days

issue commentRazrFalcon/ttf-parser

Point direction request

All I was able to find is FT_Outline_Get_Orientation, which indeed returns FT_ORIENTATION_POSTSCRIPT for d and FT_ORIENTATION_TRUETYPE for b. I will see how freetype implements it.

PS: And could you please tone down your demands. This is open source and not a commercial support line.

mooman219

comment created time in 11 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha bd13d6dfefb749d470f78d96fb5e2371b35f6466

Merge safe-geom crate into this one.

view details

push time in 11 days

push eventRazrFalcon/rustybuzz

Evgeniy Reizner

commit sha 3dcf7905948a16a8afaf0252eaa06526e582337b

Update readme.

view details

push time in 11 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 3574cafdec7b872c10c17fc8cb6681669441ab75

Fix clippy warnings.

view details

Evgeniy Reizner

commit sha 20423ebc170c25dd36e2743e39eedee5a1016dc4

Rename raster_pipeline into pipeline.

view details

Evgeniy Reizner

commit sha 6c3ee469b4417a868898d287fb7af549261ab389

Enable the std feature for wide.

view details

Evgeniy Reizner

commit sha 570eb73bf01ee5f7aa88df6bf75015004292981f

Merge safe-geom crate into this one.

view details

push time in 11 days

PR opened Lokathor/safe_arch

Minor documentation fix.
+1 -1

0 comment

1 changed file

pr created time in 11 days

create barnchRazrFalcon/safe_arch

branch : fix-1

created branch time in 11 days

fork RazrFalcon/safe_arch

Exposes arch-specific intrinsics as safe function (via cfg).

fork in 11 days

issue commentdeadlinks/cargo-deadlinks

Error on the crate with a custom bin and lib

@jyn514 I guess. That was long time ago. I was simply surprised by the panic.

RazrFalcon

comment created time in 11 days

pull request commentLokathor/wide

Lower i32x8/u32x8 SSE requirements from SSSE3 down to SSE2.

This was pretty straight forward, since you actually not using any SSSE3 specific intrinsics.

I've also fixed some formatting issues.

RazrFalcon

comment created time in 11 days

PR opened Lokathor/wide

Lower i32x8/u32x8 SSE requirements from SSSE3 down to SSE2.

Closes #76

+117 -139

0 comment

3 changed files

pr created time in 11 days

create barnchRazrFalcon/wide

branch : i32x8-sse2

created branch time in 11 days

PR closed Lokathor/wide

Add SSSE3 fallback to f32x8::trunc_int
+6 -4

2 comments

3 changed files

RazrFalcon

pr closed time in 11 days

pull request commentLokathor/wide

Add SSSE3 fallback to f32x8::trunc_int

Yes, I'm using -Ctarget-cpu=native/haswell.

I guess this patch would be superseded by the next one.

RazrFalcon

comment created time in 11 days

PR opened Lokathor/wide

Add SSSE3 fallback to f32x8::trunc_int
+6 -4

0 comment

3 changed files

pr created time in 11 days

create barnchRazrFalcon/wide

branch : trunc-sse

created branch time in 11 days

issue closedLokathor/wide

How to create i32x8 from two m128i?

I wanted to add SSSE3 fallback to f32x8::trunc_int, so I've naively wrote:

else if #[cfg(target_feature="ssse3")] {
    i32x8 { sse0: truncate_m128_to_m128i(self.sse0), sse1: truncate_m128_to_m128i(self.sse1) }

But since sse0/sse1 are private, I cannot do this. Is there an another way?

closed time in 11 days

RazrFalcon

issue commentLokathor/wide

How to create i32x8 from two m128i?

Ok, will do.

RazrFalcon

comment created time in 11 days

issue closedLokathor/wide

How to create i32x8 from two m128i?

I wanted to add SSSE3 fallback to f32x8::trunc_int, so I've naively wrote:

else if #[cfg(target_feature="ssse3")] {
    i32x8 { sse0: truncate_m128_to_m128i(self.sse0), sse1: truncate_m128_to_m128i(self.sse1) }

But since sse0/sse1 are private, I cannot do this. Is there an another way?

closed time in 11 days

RazrFalcon

issue commentLokathor/wide

How to create i32x8 from two m128i?

Dupe?!

RazrFalcon

comment created time in 11 days

issue openedLokathor/wide

How to create i32x8 from two m128i?

I wanted to add SSSE3 fallback to f32x8::trunc_int, so I've naively wrote:

else if #[cfg(target_feature="ssse3")] {
    i32x8 { sse0: truncate_m128_to_m128i(self.sse0), sse1: truncate_m128_to_m128i(self.sse1) }

But since sse0/sse1 are private, I cannot do this. Is there an another way?

created time in 11 days

issue openedLokathor/wide

How to create i32x8 from two m128i?

I wanted to add SSSE3 fallback to f32x8::trunc_int, so I've naively wrote:

else if #[cfg(target_feature="ssse3")] {
    i32x8 { sse0: truncate_m128_to_m128i(self.sse0), sse1: truncate_m128_to_m128i(self.sse1) }

But since sse0/sse1 are private, I cannot do this. Is there an another way?

created time in 11 days

issue openedLokathor/wide

SSE2 support i32x8/u32x8

I'm trying to switch from f32x4 to f32x8 in my project, which is fairly straight-forward thanks to wide's fallback mechanism. But I also use a lot of i32x8/u32x8, which means that on SSE2 I'm stuck with scalar, which is very slow.

Is there a reason why i32x8/u32x8 doesn't support SSE2 fallback? f32x8 also uses two m128, but requires only SSE2 and not SSSE3. I can try implementing SSE2 fallback for i32x8/u32x8 if you're interested.

created time in 11 days

push eventRazrFalcon/wide

Evgeniy Reizner

commit sha e95d8f5131b98193fc3d9d48c574dacb47a9a721

Remove unnecessary rounding from f32xN::trunc. (#74)

view details

Evgeniy Reizner

commit sha cf21c1510d7f298148d3f350668788f40744c265

Add f32xN::recip and f32xN::recip_sqrt (#71) Closes #69

view details

Lokathor

commit sha 6d7c364ade36b584bc7a74e0b28a415667403b82

(cargo-release) version 0.6.1

view details

Lokathor

commit sha fb7c9d4ce0254fc990818b00041057dbb7400620

(cargo-release) start next development iteration 0.6.2-alpha.0

view details

push time in 11 days

issue commentRazrFalcon/ttf-parser

Point direction request

@hasenbanck Yes, that font version doesn't mirror d for b. But it doesn't seem like the old font itself have a bug. ttf-parser, freetype and harfbuzz are processing it correctly. Something is missing between ttf-parser and fontdue.

mooman219

comment created time in 12 days

issue commentRazrFalcon/ttf-parser

Point direction request

Here is 225 'b'. The directions is still correct.

FreeType:

ft

ttf-parser:

ttfp

I don't think any solution here is elegant,

First, we have to prove that there are an actual bug.

mooman219

comment created time in 12 days

issue commentRazrFalcon/ttf-parser

Point direction request

So in the end, mirroring and direction are the same as in FreeType. Maybe the problem is somewhere else?

mooman219

comment created time in 12 days

issue commentRazrFalcon/ttf-parser

Point direction request

So I've added points enumeration:

FreeType:

ft

ttf-parser:

ttfp

As you can see, the points order/direction is roughly the same. Looks like FreeType does some slight changes to the output (not rounding). I will have to run FreeType under debugger to figure it out.

mooman219

comment created time in 12 days

issue commentRazrFalcon/ttf-parser

Point direction request

Very strange. FreeType produces the same outline, but the points order and values are very different. Looks like the problem no in mirroring, bit in the second contour points order.

I will try to write a tool that will help me with debugging this issue tomorrow.

The most interesting part, is that if you export both outlines (ttf-parser one and ft2 one) to SVG, they will be filled equally, while having different points. Here: issue-43-test.zip This is probably the reason why I'm missed this.

mooman219

comment created time in 12 days

push eventRazrFalcon/rustybuzz

Evgeniy Reizner

commit sha e435610b007357c079d826de1c53e6556252f6ad

Update readme.

view details

push time in 12 days

push eventRazrFalcon/rustybuzz

Laurenz

commit sha 9533c2759826adc492bc23f28d887f5c6330205b

Port fallback shaper.

view details

push time in 12 days

PR merged RazrFalcon/rustybuzz

Port fallback shaper.

Since I'm excited to use this in a full-Rust WASM scenario, I decided to try and tackle one of the things on the roadmap that were marked as easy. I tried making the most straightforward, parallel adaption of the original C++ to make it not too hard to review. (Part of this is that I kept the order of functions as originally, even though I guess in Rust the general -> specific order is more common than the C-compiler-inflicted specific -> general.)

Some things I weren't totally sure about, I marked these with NOTE(laurmaedje). One particular thing I hope I didn't confuse are the Modified and Canonical combining classes.

+601 -508

5 comments

12 changed files

laurmaedje

pr closed time in 12 days

pull request commentRazrFalcon/rustybuzz

Port fallback shaper.

@alerque Thanks. Anything would help. But I would say that testing is the most crucial part. Yes, HB has tons of tests, but sadly, this is not enough. Shaping is way too complicated.

laurmaedje

comment created time in 12 days

pull request commentRazrFalcon/rustybuzz

Port fallback shaper.

@laurmaedje Thanks. Yes, I do have time for reviews. I just can't work full-time on this project right now.

Moreover, I would be glad if someone would help me with this project. This is mostly a tedious rewrite with minimal changes. And you can take any task from the roadmap. The only unpublished thing I have is kerx support (which is very similar to mort/morx and should be done first). And this is basically all the Apple tables support we need. The real problem is testing, as mentioned in the readme.

As for the TrueType tables support, I think all the remaining tables should be implemented on the rustybuzz side first, and then moved to ttf-parser, if we would be able to figure out any sane API. For example, I have no idea what kind of API we could have for GSUB/GPOS parsing, because of how tight it works with hb_buffer_t in hb. But first, you should use the next branch (a bit outdated right now) of ttf-parser, which has the parser module public.

Feel free to ask any questions.

laurmaedje

comment created time in 12 days

delete branch RazrFalcon/tiny-skia

delete branch : fix-msrv

delete time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 51d5aaff8c30c5ab06a4deb34ec288548d95e65d

Use checked pixels access everywhere.

view details

Evgeniy Reizner

commit sha e8df87728619ff1eba0ba14ddb94b6a9e4ccb896

Fix MSRV.

view details

push time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha f377c940fd35241cf7af1a36ce4be36162c61b6a

f

view details

Evgeniy Reizner

commit sha b8f57cdc3a94834a641f64b0004b2a2038612110

f

view details

Evgeniy Reizner

commit sha f1ad6b5e6c009b6a58b9aea73871c1ada927e5ce

f

view details

push time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha bc29a65acf6cf1fec2043b09cb5c84f5a1b532dd

f

view details

push time in 13 days

issue commentRazrFalcon/ttf-parser

Point direction request

  1. Can I reproduce the issues with Comfortaa-Regular.ttf?
  2. Can you post a link to the specification were this behavior is explained?
  3. Emitting points in reverse is highly problematic, since we do not allocate, therefore we have to store points on stack first, which is quite limiting.
  4. I will check what freetype does in this case.
mooman219

comment created time in 13 days

push eventRazrFalcon/safe-geom

Evgeniy Reizner

commit sha 8a96c1ba8b29a041eb5ebe256c0f246960e3711e

Fix MSRV support.

view details

push time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 5087b6310f24cf24680dfc04ff37e210536d45a5

f

view details

push time in 13 days

create barnchRazrFalcon/tiny-skia

branch : fix-msrv

created branch time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 63da700ff98684467505ae526f5d826ca26e7088

Use the 'wide' crate for SIMD.

view details

push time in 13 days

delete branch RazrFalcon/wide

delete branch : f32x4-trunc

delete time in 13 days

delete branch RazrFalcon/wide

delete branch : fix-trunc

delete time in 13 days

delete branch RazrFalcon/wide

delete branch : f32xN-recip

delete time in 13 days

pull request commentLokathor/wide

Add f32xN::recip and f32xN::recip_sqrt

tiny-skia now uses wide for SIMD handling. This removed a lot of unsafe code. And since wide already support some 256 bit types, looks like it a good opportunity to try them out.

RazrFalcon

comment created time in 13 days

push eventRazrFalcon/tiny-skia

Evgeniy Reizner

commit sha 87195fd6bba8472a735903757791baf7177def5c

Use the 'wide' crate for SIMD.

view details

push time in 13 days

issue commentLokathor/wide

Should Debug include the type name?

Yes, bu this is what Debug does by default. Also, (1, 2, 3, 4) can be either i32x4 or i64x4. Hard to guess.

This is just a suggestion, by the way.

RazrFalcon

comment created time in 13 days

issue openedLokathor/wide

Should Debug include the type name?

The Debug output for, let's say, f32x4 looks like this:

(1.95763, 1.95276, 1.9478897, 1.9430195)

Maybe it would be better to have something like:

f32x4(1.95763, 1.95276, 1.9478897, 1.9430195)

created time in 13 days

issue commentRazrFalcon/cargo-bloat

Windows support

I don't thinks so. I don't know what cargo would be building beforehand. Unless we, somehow, can check what target will be used.

smmalis37

comment created time in 13 days

pull request commentLokathor/wide

Add f32xN::recip and f32xN::recip_sqrt

Thanks. No problem.

RazrFalcon

comment created time in 13 days

pull request commentLokathor/wide

Add f32xN::recip and f32xN::recip_sqrt

Ping?

RazrFalcon

comment created time in 13 days

pull request commentRazrFalcon/rustybuzz

Port fallback shaper.

Thanks! I didn't thought anyone would work on this. And I really don't have to time for this project right now, sadly.

Since all tests are passed, I don't have any real complains.

This branch is never tested.

Yes, this is pretty common. You can remove all those comments. We can check it via cargo tarpaulin at any time.

If you plan to write any tests - write them to the harfbuzz first. I don't want to have any rustybuzz-specific tests. And it will benefit both projects.

laurmaedje

comment created time in 13 days

Pull request review commentRazrFalcon/rustybuzz

Port fallback shaper.

+use crate::{ffi, Direction, Font};+use crate::buffer::{Buffer, GlyphPosition};+use crate::unicode::{modified_combining_class, CanonicalCombiningClass, GeneralCategory, Space};+use crate::ot::*;++fn recategorize_combining_class(u: u32, mut class: u8) -> u8 {+    use CanonicalCombiningClass as Class;+    use modified_combining_class as mcc;++    if class >= 200 {+        return class;+    }++    // Thai / Lao need some per-character work.+    if u & !0xFF == 0x0E00 {+        // NOTE(laurmaedje): This branch is never tested.+        if class == 0 {+            match u {+                0x0E31 |+                0x0E34 |+                0x0E35 |+                0x0E36 |+                0x0E37 |+                0x0E47 |+                0x0E4C |+                0x0E4D |+                0x0E4E => class = Class::AboveRight as u8,++                0x0EB1 |+                0x0EB4 |+                0x0EB5 |+                0x0EB6 |+                0x0EB7 |+                0x0EBB |+                0x0ECC |+                0x0ECD => class = Class::Above as u8,++                0x0EBC => class = Class::Below as u8,++                _ => {}+            }+        } else {+            // Thai virama is below-right+            if u == 0x0E3A {+                class = Class::BelowRight as u8;+            }+        }+    }++    match class {+        // Hebrew+        mcc::CCC10 => Class::Below as u8,         // sheva+        mcc::CCC11 => Class::Below as u8,         // hataf segol+        mcc::CCC12 => Class::Below as u8,         // hataf patah+        mcc::CCC13 => Class::Below as u8,         // hataf qamats+        mcc::CCC14 => Class::Below as u8,         // hiriq+        mcc::CCC15 => Class::Below as u8,         // tsere+        mcc::CCC16 => Class::Below as u8,         // segol+        mcc::CCC17 => Class::Below as u8,         // patah+        mcc::CCC18 => Class::Below as u8,         // qamats+        mcc::CCC20 => Class::Below as u8,         // qubuts+        mcc::CCC22 => Class::Below as u8,         // meteg+        mcc::CCC23 => Class::AttachedAbove as u8, // rafe+        mcc::CCC24 => Class::AboveRight as u8,    // shin dot+        mcc::CCC25 => Class::AboveLeft as u8,     // sin dot+        mcc::CCC19 => Class::AboveLeft as u8,     // holam+        mcc::CCC26 => Class::Above as u8,         // point varika+        mcc::CCC21 => class,                      // dagesh++        // Arabic and Syriac+        mcc::CCC27 => Class::Above as u8, // fathatan+        mcc::CCC28 => Class::Above as u8, // dammatan+        mcc::CCC30 => Class::Above as u8, // fatha+        mcc::CCC31 => Class::Above as u8, // damma+        mcc::CCC33 => Class::Above as u8, // shadda+        mcc::CCC34 => Class::Above as u8, // sukun+        mcc::CCC35 => Class::Above as u8, // superscript alef+        mcc::CCC36 => Class::Above as u8, // superscript alaph+        mcc::CCC29 => Class::Below as u8, // kasratan+        mcc::CCC32 => Class::Below as u8, // kasra++        // Thai+        mcc::CCC103 => Class::BelowRight as u8, // sara u / sara uu+        mcc::CCC107 => Class::AboveRight as u8, // mai++        // Lao+        mcc::CCC118 => Class::Below as u8, // sign u / sign uu+        mcc::CCC122 => Class::Above as u8, // mai++        // Tibetian+        mcc::CCC129 => Class::Below as u8, // sign aa+        mcc::CCC130 => Class::Above as u8, // sign i+        mcc::CCC132 => Class::Below as u8, // sign u++        _ => class,+    }+}++#[no_mangle]+pub extern "C" fn _rb_ot_shape_fallback_mark_position_recategorize_marks(+    _: *const ffi::rb_ot_shape_plan_t,+    _: *mut ffi::rb_font_t,+    buffer: *mut ffi::rb_buffer_t,+) {+    let buffer = Buffer::from_ptr_mut(buffer);+    for info in &mut buffer.info {+        if info.general_category() == GeneralCategory::NonspacingMark {+            let mut class = info.modified_combining_class();+            class = recategorize_combining_class(info.codepoint, class);+            info.set_modified_combining_class(class);+        }+    }+}++fn zero_mark_advances(+    buffer: &mut Buffer,+    start: usize,+    end: usize,+    adjust_offsets_when_zeroing: bool,+) {+    // NOTE(laurmaedje): This whole function is never tested.+    for (info, pos) in buffer.info[start..end].iter().zip(&mut buffer.pos[start..end]) {+        if info.general_category() == GeneralCategory::NonspacingMark {+            if adjust_offsets_when_zeroing {+                pos.x_offset -= pos.x_advance;+                pos.y_offset -= pos.y_advance;+            }+            pos.x_advance = 0;+            pos.y_advance = 0;+        }+    }+}++fn position_mark(+    _: &ShapePlan,+    font: &Font,+    direction: Direction,+    codepoint: u32,+    pos: &mut GlyphPosition,+    base_extents: &mut ffi::rb_glyph_extents_t,+    combining_class: CanonicalCombiningClass,+) {+    use CanonicalCombiningClass as Class;++    let mark_extents = match font.glyph_extents(codepoint) {+        Some(extents) => extents,+        None => return,+    };++    let y_gap = font.units_per_em() / 16;+    pos.x_offset = 0;+    pos.y_offset = 0;++    // We don't position LEFT and RIGHT marks.++    // X positioning+    match combining_class {+        Class::DoubleBelow |+        Class::DoubleAbove if direction.is_horizontal() => {+            pos.x_offset += base_extents.x_bearing+                + if direction.is_forward() { base_extents.width } else { 0 }+                - mark_extents.width / 2 - mark_extents.x_bearing;+        }++        Class::AttachedBelowLeft |+        Class::BelowLeft |+        Class::AboveLeft => {+            // Left align.+            pos.x_offset += base_extents.x_bearing - mark_extents.x_bearing;+        }++        Class::AttachedAboveRight |+        Class::BelowRight |+        Class::AboveRight => {+            // Right align.+            pos.x_offset += base_extents.x_bearing + base_extents.width+                - mark_extents.width - mark_extents.x_bearing;+        }++        Class::AttachedBelow |+        Class::AttachedAbove |+        Class::Below |+        Class::Above |+        _ => {+            // Center align.+            pos.x_offset += base_extents.x_bearing+                + (base_extents.width - mark_extents.width) / 2+                - mark_extents.x_bearing;+        }+    }++    let is_attached = matches!(+        combining_class,+        Class::AttachedBelowLeft |+        Class::AttachedBelow |+        Class::AttachedAbove |+        Class::AttachedAboveRight+    );++    // Y positioning.+    match combining_class {+        Class::DoubleBelow |+        Class::BelowLeft |+        Class::Below |+        Class::BelowRight |+        Class::AttachedBelowLeft |+        Class::AttachedBelow => {+            if !is_attached {+                // Add gap.+                base_extents.height -= y_gap;+            }++            pos.y_offset = base_extents.y_bearing + base_extents.height+                - mark_extents.y_bearing;++            // Never shift up "below" marks.+            if (y_gap > 0) == (pos.y_offset > 0) {+                base_extents.height -= pos.y_offset;+                pos.y_offset = 0;+            }++            base_extents.height += mark_extents.height;+        }++        Class::DoubleAbove |+        Class::AboveLeft |+        Class::Above |+        Class::AboveRight |+        Class::AttachedAbove |+        Class::AttachedAboveRight => {+            if !is_attached {+                // Add gap.+                base_extents.y_bearing += y_gap;+                base_extents.height -= y_gap;+            }++            pos.y_offset = base_extents.y_bearing+                - (mark_extents.y_bearing + mark_extents.height);++            // Don't shift down "above" marks too much.+            if (y_gap > 0) != (pos.y_offset > 0) {+                // NOTE(laurmaedje): In the original this was "unsigned int".+                // Does it matter? I don't think so, I have to cast back anyway, otherwise.

Looks like a minor hb bug. I think it should be reported just in case.

laurmaedje

comment created time in 13 days

PullRequestReviewEvent

Pull request review commentRazrFalcon/rustybuzz

Port fallback shaper.

 impl<'a> Font<'a> {         rb_font_get_advance(self.as_ptr(), glyph, 0)     } +    pub(crate) fn glyph_v_advance(&self, glyph: u32) -> i32 {+        // NOTE(laurmaedje): Is it correct to negate here?

Yes, it should be fine.

laurmaedje

comment created time in 13 days

more