servo / font-kit

A cross-platform font loading library written in Rust
Apache License 2.0
693 stars 104 forks source link

`font.outline()` renders a glyph in reversed shape #247

Open kmoon2437 opened 3 months ago

kmoon2437 commented 3 months ago

I'm using this library with raqote for text rendering. I wrote the code below:

use std::fs::File;
use font_kit::font::Font;
use font_kit::hinting::HintingOptions;
use font_kit::outline::OutlineSink;
use raqote::{
    Path, PathBuilder,
    Point, Source, SolidSource,
    StrokeStyle, LineCap, LineJoin,
    DrawTarget, DrawOptions
};
use pathfinder_geometry::vector::Vector2F;
use pathfinder_geometry::line_segment::LineSegment2F;

pub struct TextPathBuilder {
    pb: PathBuilder,
    font_scale: f32,
    start: Point
}

impl TextPathBuilder {
    pub fn new(font_size: f32, start: Point, units_per_em: u32) -> Self {
        return Self {
            pb: PathBuilder::new(),
            font_scale: font_size / units_per_em as f32,
            start
        };
    }

    fn calc_point(&self, point: f32) -> f32 {
        return point * self.font_scale;
    }

    fn calc_x(&self, point: f32) -> f32 {
        return self.calc_point(point) + self.start.x;
    }

    fn calc_y(&self, point: f32) -> f32 {
        return self.calc_point(point) + self.start.y;
    }

    pub fn finish(self) -> Path {
        return self.pb.finish();
    }
}

impl OutlineSink for TextPathBuilder {
    fn move_to(&mut self, to: Vector2F) {
        self.pb.move_to(self.calc_x(to.x()), self.calc_y(to.y()));
    }

    fn line_to(&mut self, to: Vector2F) {
        self.pb.line_to(self.calc_x(to.x()), self.calc_y(to.y()));
    }

    fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
        self.pb.quad_to(self.calc_x(ctrl.x()), self.calc_y(ctrl.y()), self.calc_x(to.x()), self.calc_y(to.y()));
    }

    fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
        self.pb.cubic_to(self.calc_x(ctrl.from().x()), self.calc_y(ctrl.from().y()), self.calc_x(ctrl.to().x()), self.calc_y(ctrl.to().y()), self.calc_x(to.x()), self.calc_y(to.y()));
    }

    fn close(&mut self) {
        self.pb.close();
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut file = File::open("tmp/NotoSansCJK-Medium.otf")?;
    let font = Font::from_file(&mut file, 0)?;

    let mut tpb = TextPathBuilder::new(70.0, Point::new(10.0, 10.0), font.metrics().units_per_em);
    font.outline(font.glyph_for_char('9').unwrap(), HintingOptions::None, &mut tpb)?;
    let path = tpb.finish();

    let mut dt = DrawTarget::new(100, 100);

    let color1 = Source::Solid(SolidSource::from_unpremultiplied_argb(255, 255, 255, 255));
    let color2 = Source::Solid(SolidSource::from_unpremultiplied_argb(255, 0x2a, 0xbd, 0x71));
    let stroke = StrokeStyle {
        cap: LineCap::Butt,
        join: LineJoin::Round,
        width: 10.0,
        miter_limit: 10.0,
        ..Default::default()
    };
    let draw_options = DrawOptions {
        ..Default::default()
    };

    dt.stroke(&path, &color1, &stroke, &draw_options);
    dt.fill(&path, &color2, &draw_options);

    dt.write_png("example.png")?;

    return Ok(());
}

However, the output is reversed "9".

I fixed TextPathBuilder like this:

pub struct TextPathBuilder {
    pb: PathBuilder,
    font_scale: f32,
    font_size: f32,
    start: Point
}

impl TextPathBuilder {
    pub fn new(font_size: f32, start: Point, units_per_em: u32) -> Self {
        return Self {
            pb: PathBuilder::new(),
            font_scale: font_size / units_per_em as f32,
            font_size,
            start
        };
    }

    fn calc_point(&self, point: f32) -> f32 {
        return point * self.font_scale;
    }

    fn calc_x(&self, point: f32) -> f32 {
        return self.calc_point(point) + self.start.x;
    }

    fn calc_y(&self, point: f32) -> f32 {
        return -self.calc_point(point) + self.font_size + self.start.y;
    }

    pub fn finish(self) -> Path {
        return self.pb.finish();
    }
}

and the image is generated correctly.

Did I fix the problem correctly?