### ----------------------------------------------------
### -- HarfBuzz
### -- Licenses: Artistic-2.0
### -- Authors: David Warring
### -- File: lib/HarfBuzz/Font.rakumod
### ----------------------------------------------------
 #| HarfBuzz font data-type
unit class HarfBuzz::Font;

use HarfBuzz::Raw;
use HarfBuzz::Raw::Defs :types;
use HarfBuzz::Buffer;
use HarfBuzz::Face;
use HarfBuzz::Feature;
use NativeCall;

has HarfBuzz::Face() $.face handles<Blob> is built;
has hb_font $.raw;
has HarfBuzz::Feature() @!features is built;
has UInt $.gen is built; # mutation generation

=begin pod

=head2 Synopsis

   use HarfBuzz::Font;
   my HarfBuzz::Font() .= %( :$file, :@features, :$size, :@scale );

=head2 Description

A HarfBuzz font is used for shaping text (See class L<HarfBuzz::Shaper>).

Fonts may also be subsetted (Reduced to a smaller set of glyphs; see module L<HarfBuzz::Subset>).

=head2 Methods

=end pod

multi submethod TWEAK(:$!face!, |c) { self!build: |c }
multi submethod TWEAK(IO:D() :$file!, UInt:D :$index = 0, |c) {
    $!face = %( :$file, :$index );
    self!build: |c
}
multi submethod TWEAK(Blob:D :$buf!, UInt:D :$index = 0, |c) {
    $!face = %( :$buf, :$index );
    self!build: |c
}

method !build(:@scale, :$size = 12e0) is hidden-from-backtrace {
    $!raw //= hb_font::create($!face.raw);
    $!raw.reference;

    if @scale {
        self.scale = @scale
    }
    else {
        $!raw.get-scale(my int32 $x, my int32 $y);
        if $x == 0 || $y == 0 {
            $x ||= 2048;
            $y ||= $x;
            $!raw.set-scale($x, $y);
        }
    }
    $!raw.set-size($size.Num) if $size;
    $!gen = 1;
}

multi method COERCE(%opts) {
    my HarfBuzz::Feature @features;
    @features = .map({HarfBuzz::Feature.COERCE: $_})
        with %opts<features>;
    self.new: |%opts, :@features;
}

submethod DESTROY {
    $!raw.destroy;
}

#| Gets or sets x and y scale
method scale is rw returns List {
    Proxy.new(
        FETCH => { $!raw.get-scale(my int32 $x, my int32 $y); ($x, $y || $x) },
        STORE => -> $, [ $x, $y = $x ] {
            $!gen++;
            $!raw.set-scale($x.Int, $y.Int);
        }
    );
}

#| Gets or sets the font size
method size is rw returns Numeric {
    Proxy.new(
        FETCH => { $!raw.get-size },
        STORE => -> $, Num:D() $_ {
            $!gen++;
            $!raw.set-size($_);
        }
    );
}

#| Add font features
method add-features(HarfBuzz::Feature() @features --> Array) {
    $!gen++;
    @!features.append: @features;
}

method add-feature(HarfBuzz::Feature() $feature --> Array) {
    $!gen++;
    @!features.push: $feature;
}


#| Returns the glyph name for a glyph identifier
method glyph-name(UInt:D $gid --> Str) {
    my buf8 $name-buf .= allocate(64);
    $!raw.get-glyph-name($gid, $name-buf, $name-buf.elems);
    nativecast(Str, $name-buf);
}

#| Returns the glyph identifier for a given glyph name
method glyph-from-name(Str:D $name --> UInt) {
    my Blob $buf = $name.encode;
    my hb_codepoint $gid;
    $!raw.get-glyph-from-name($buf, $buf.bytes, $gid);
    $gid;
}

#| Returns metrics for a glyph
method glyph-extents(UInt:D $gid --> hb_glyph_extents) {
    my hb_glyph_extents $glyph-extents .= new;
    $!raw.get-glyph-extents($gid, $glyph-extents);
    $glyph-extents;
}
=begin pod
=para Note that `hb_glyph_extents` is a `CStruct` with `Num` attributes `x-advance`, `y-advance`, `x-offset` and `y-offset`.

=end pod

#| Shape a buffer using this font
method shape(HarfBuzz::Buffer:D :$buf!) {
    $buf.reset if $buf.shaped;
    my buf8 $feats-buf .= allocate(nativesizeof(hb_feature) * +@!features);
    my hb_features $feats = nativecast(hb_features, $feats-buf);
    $feats[.key] = .value.raw for @!features.pairs;
    $!raw.shape($buf.raw, $feats-buf, +@!features);
}



