// Copyright 2018 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

#include "src/pdf/SkPDFSubsetFont.h"

#if defined(SK_PDF_USE_HARFBUZZ_SUBSET)

#include "include/core/SkData.h"
#include "include/core/SkStream.h"
#include "include/core/SkTypeface.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMalloc.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "src/pdf/SkPDFGlyphUse.h"

#include "hb.h"  // NO_G3_REWRITE
#include "hb-subset.h"  // NO_G3_REWRITE

#include <cstddef>
#include <memory>
#include <utility>

namespace {

using HBBlob = std::unique_ptr<hb_blob_t, SkFunctionObject<hb_blob_destroy>>;
using HBFace = std::unique_ptr<hb_face_t, SkFunctionObject<hb_face_destroy>>;
using HBSubsetInput = std::unique_ptr<hb_subset_input_t, SkFunctionObject<hb_subset_input_destroy>>;
using HBSet = std::unique_ptr<hb_set_t, SkFunctionObject<hb_set_destroy>>;

HBBlob stream_to_blob(std::unique_ptr<SkStreamAsset> asset) {
    size_t size = asset->getLength();
    HBBlob blob;
    if (const void* base = asset->getMemoryBase()) {
        blob.reset(hb_blob_create(const_cast<char*>(static_cast<const char*>(base)), SkToUInt(size),
                                  HB_MEMORY_MODE_READONLY, asset.release(),
                                  [](void* p) { delete (SkStreamAsset*)p; }));
    } else {
        void* ptr = size ? sk_malloc_throw(size) : nullptr;
        asset->read(ptr, size);
        blob.reset(hb_blob_create((char*)ptr, SkToUInt(size),
                                  HB_MEMORY_MODE_READONLY, ptr, sk_free));
    }
    SkASSERT(blob);
    hb_blob_make_immutable(blob.get());
    return blob;
}

sk_sp<SkData> to_data(HBBlob blob) {
    if (!blob) {
        return nullptr;
    }
    unsigned int length;
    const char* data = hb_blob_get_data(blob.get(), &length);
    if (!data || !length) {
        return nullptr;
    }
    return SkData::MakeWithProc(data, SkToSizeT(length),
                                [](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); },
                                blob.release());
}

HBFace make_subset(hb_subset_input_t* input, hb_face_t* face, bool retainZeroGlyph) {
    // TODO: When possible, check if a font is 'tricky' with FT_IS_TRICKY.
    // If it isn't known if a font is 'tricky', retain the hints.
    unsigned int flags = HB_SUBSET_FLAGS_RETAIN_GIDS;
    if (retainZeroGlyph) {
        flags |= HB_SUBSET_FLAGS_NOTDEF_OUTLINE;
    }
    hb_subset_input_set_flags(input, flags);
    return HBFace(hb_subset_or_fail(face, input));
}

sk_sp<SkData> subset_harfbuzz(const SkTypeface& typeface, const SkPDFGlyphUse& glyphUsage) {
    int index = 0;
    std::unique_ptr<SkStreamAsset> typefaceAsset = typeface.openStream(&index);
    HBFace face;
    HBBlob blob(stream_to_blob(std::move(typefaceAsset)));
    // hb_face_create always succeeds. Check that the format is minimally recognized first.
    // See https://github.com/harfbuzz/harfbuzz/issues/248
    unsigned int num_hb_faces = hb_face_count(blob.get());
    if (0 < num_hb_faces && (unsigned)index < num_hb_faces) {
        face.reset(hb_face_create(blob.get(), (unsigned)index));
        // Check the number of glyphs as a basic sanitization step.
        if (face && hb_face_get_glyph_count(face.get()) == 0) {
            face.reset();
        }
    }

    HBSubsetInput input(hb_subset_input_create_or_fail());
    SkASSERT(input);
    if (!face || !input) {
        return nullptr;
    }
    hb_set_t* glyphs = hb_subset_input_glyph_set(input.get());
    glyphUsage.getSetValues([&glyphs](unsigned gid) { hb_set_add(glyphs, gid);});

    HBFace subset = make_subset(input.get(), face.get(), glyphUsage.has(0));
    if (!subset) {
        return nullptr;
    }
    HBBlob result(hb_face_reference_blob(subset.get()));
    return to_data(std::move(result));
}

}  // namespace

sk_sp<SkData> SkPDFSubsetFont(const SkTypeface& typeface, const SkPDFGlyphUse& glyphUsage) {
    return subset_harfbuzz(typeface, glyphUsage);
}

#else

sk_sp<SkData> SkPDFSubsetFont(const SkTypeface&, const SkPDFGlyphUse&) {
    return nullptr;
}

#endif  // defined(SK_PDF_USE_HARFBUZZ_SUBSET)
