Skip to content

Commit

Permalink
Correct vertical offset for Apple Color Emoji (#792)
Browse files Browse the repository at this point in the history
Experimentation indicates that CoreText special cases the Apple Color
Emoji font and adds a 100 font unit vertical offset when rendering. This
PR adds a hack to mimic that behavior for correct emoji placement on
Mac.

Also fixes a tiny general sbix bug where we were adding rather than
subtracting the height of the image.

parley's vello_editor with this fix applied:
<img width="618" alt="Screenshot 2025-01-17 at 10 39 45 AM"
src="https://github.com/user-attachments/assets/a263e2b6-efbe-4770-b2b6-e72a5982a04a"
/>
  • Loading branch information
dfrg authored Jan 17, 2025
1 parent 0e79178 commit d5ebe12
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 11 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ This is the first step towards providing richer color functionality, better hand
- Inference conflict when using Kurbo's `schemars` feature ([#733][] by [@ratmice][])
- Detection of PNG format bitmap fonts, primarily for Apple systems ([#740][] by [@LaurenzV])
- Support image extend modes, nearest-neighbor sampling and alpha ([#766][] by [@dfrg])
- Correct vertical offset for Apple Color Emoji ([#792][] by [@dfrg])

## [0.3.0][] - 2024-10-04

Expand Down Expand Up @@ -219,8 +220,9 @@ This release has an [MSRV][] of 1.75.
[#747]: https://github.com/linebender/vello/pull/747
[#754]: https://github.com/linebender/vello/pull/754
[#756]: https://github.com/linebender/vello/pull/756
[#766]: https://github.com/linebender/vello/pull/766
[#757]: https://github.com/linebender/vello/pull/757
[#766]: https://github.com/linebender/vello/pull/766
[#792]: https://github.com/linebender/vello/pull/792

[Unreleased]: https://github.com/linebender/vello/compare/v0.3.0...HEAD
<!-- Note that this still comparing against 0.2.0, because 0.2.1 is a cherry-picked patch -->
Expand Down
2 changes: 1 addition & 1 deletion vello/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ impl<'a> DrawGlyphs<'a> {
bitmap::Origin::TopLeft => transform,
bitmap::Origin::BottomLeft => transform.pre_translate(Vec2 {
x: 0.,
y: f64::from(image.height),
y: -f64::from(image.height),
}),
};
if let Some(glyph_transform) = self.run.glyph_transform {
Expand Down
56 changes: 47 additions & 9 deletions vello/src/scene/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use skrifa::{
types::{GlyphId, Tag},
FontData, TableProvider,
},
MetadataProvider,
string::StringId,
FontRef, MetadataProvider,
};

/// Set of strikes, each containing embedded bitmaps of a single size.
Expand Down Expand Up @@ -45,6 +46,7 @@ impl<'a> BitmapStrikes<'a> {
let kind = match format {
BitmapFormat::Sbix => StrikesKind::Sbix(
font.sbix().ok()?,
SbixKind::from_font(font),
font.glyph_metrics(Size::unscaled(), LocationRef::default()),
),
BitmapFormat::Cbdt => {
Expand Down Expand Up @@ -72,7 +74,7 @@ impl<'a> BitmapStrikes<'a> {
pub fn len(&self) -> usize {
match &self.0 {
StrikesKind::None => 0,
StrikesKind::Sbix(sbix, _) => sbix.strikes().len(),
StrikesKind::Sbix(sbix, ..) => sbix.strikes().len(),
StrikesKind::Cbdt(cbdt) => cbdt.location.bitmap_sizes().len(),
StrikesKind::Ebdt(ebdt) => ebdt.location.bitmap_sizes().len(),
}
Expand All @@ -87,8 +89,8 @@ impl<'a> BitmapStrikes<'a> {
pub fn get(&self, index: usize) -> Option<BitmapStrike<'a>> {
let kind = match &self.0 {
StrikesKind::None => return None,
StrikesKind::Sbix(sbix, metrics) => {
StrikeKind::Sbix(sbix.strikes().get(index).ok()?, metrics.clone())
StrikesKind::Sbix(sbix, kind, metrics) => {
StrikeKind::Sbix(sbix.strikes().get(index).ok()?, *kind, metrics.clone())
}
StrikesKind::Cbdt(tables) => StrikeKind::Cbdt(
tables.location.bitmap_sizes().get(index).copied()?,
Expand Down Expand Up @@ -138,11 +140,34 @@ impl<'a> BitmapStrikes<'a> {
#[derive(Clone)]
enum StrikesKind<'a> {
None,
Sbix(sbix::Sbix<'a>, GlyphMetrics<'a>),
Sbix(sbix::Sbix<'a>, SbixKind, GlyphMetrics<'a>),
Cbdt(CbdtTables<'a>),
Ebdt(EbdtTables<'a>),
}

/// Used to detect the Apple Color Emoji sbix font in order to apply a
/// workaround for CoreText's special cased vertical offset.
#[derive(Copy, Clone, PartialEq)]
enum SbixKind {
Apple,
Other,
}

impl SbixKind {
fn from_font<'a>(font: &impl skrifa::MetadataProvider<'a>) -> Self {
if font
.localized_strings(skrifa::string::StringId::POSTSCRIPT_NAME)
.next()
.map(|s| s.chars().eq("AppleColorEmoji".chars()))
.unwrap_or_default()
{
Self::Apple
} else {
Self::Other
}
}
}

/// Set of embedded bitmap glyphs of a specific size.
#[derive(Clone)]
pub struct BitmapStrike<'a>(StrikeKind<'a>);
Expand All @@ -151,7 +176,7 @@ impl<'a> BitmapStrike<'a> {
/// Returns the pixels-per-em (size) of this strike.
pub fn ppem(&self) -> f32 {
match &self.0 {
StrikeKind::Sbix(sbix, _) => sbix.ppem() as f32,
StrikeKind::Sbix(sbix, ..) => sbix.ppem() as f32,
StrikeKind::Cbdt(size, _) => size.ppem_y() as f32,
StrikeKind::Ebdt(size, _) => size.ppem_y() as f32,
}
Expand All @@ -160,7 +185,7 @@ impl<'a> BitmapStrike<'a> {
/// Returns a bitmap glyph for the given identifier, if available.
pub fn get(&self, glyph_id: GlyphId) -> Option<BitmapGlyph<'a>> {
match &self.0 {
StrikeKind::Sbix(sbix, metrics) => {
StrikeKind::Sbix(sbix, kind, metrics) => {
let glyph = sbix.glyph_data(glyph_id).ok()??;
if glyph.graphic_type() != Tag::new(b"png ") {
return None;
Expand All @@ -174,10 +199,23 @@ impl<'a> BitmapStrike<'a> {
let reader = FontData::new(png_data);
let width = reader.read_at::<u32>(16).ok()?;
let height = reader.read_at::<u32>(20).ok()?;
// CoreText appears to special case Apple Color Emoji, adding
// a 100 font unit vertical offset. We do the same but only
// when both vertical offsets are 0 to avoid incorrect
// rendering if Apple ever does encode the offset directly in
// the font.
let bearing_y = if glyf_bb.y_min == 0.0
&& glyph.origin_offset_y() == 0
&& *kind == SbixKind::Apple
{
100.0
} else {
glyf_bb.y_min
};
Some(BitmapGlyph {
data: BitmapData::Png(glyph.data()),
bearing_x: lsb,
bearing_y: glyf_bb.y_min as f32,
bearing_y,
inner_bearing_x: glyph.origin_offset_x() as f32,
inner_bearing_y: glyph.origin_offset_y() as f32,
ppem_x: ppem,
Expand Down Expand Up @@ -208,7 +246,7 @@ impl<'a> BitmapStrike<'a> {

#[derive(Clone)]
enum StrikeKind<'a> {
Sbix(sbix::Strike<'a>, GlyphMetrics<'a>),
Sbix(sbix::Strike<'a>, SbixKind, GlyphMetrics<'a>),
Cbdt(bitmap::BitmapSize, CbdtTables<'a>),
Ebdt(bitmap::BitmapSize, EbdtTables<'a>),
}
Expand Down

0 comments on commit d5ebe12

Please sign in to comment.