/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ScaledFontDWrite.h"
#include "UnscaledFontDWrite.h"
#include "PathD2D.h"
#include "gfxFont.h"
#include "Logging.h"
#include "mozilla/webrender/WebRenderTypes.h"

using namespace std;

#ifdef USE_SKIA
#include "PathSkia.h"
#include "skia/include/core/SkPaint.h"
#include "skia/include/core/SkPath.h"
#include "skia/include/ports/SkTypeface_win.h"
#endif

#include <vector>

#ifdef USE_CAIRO_SCALED_FONT
#include "cairo-win32.h"
#endif

#include "HelpersWinFonts.h"

namespace mozilla {
namespace gfx {

#define GASP_TAG 0x70736167
#define GASP_DOGRAY 0x2

static inline unsigned short
readShort(const char *aBuf)
{
  return (*aBuf << 8) | *(aBuf + 1);
}

static bool
DoGrayscale(IDWriteFontFace *aDWFace, Float ppem)
{
  void *tableContext;
  char *tableData;
  UINT32 tableSize;
  BOOL exists;
  aDWFace->TryGetFontTable(GASP_TAG, (const void**)&tableData, &tableSize, &tableContext, &exists);

  if (exists) {
    if (tableSize < 4) {
      aDWFace->ReleaseFontTable(tableContext);
      return true;
    }
    struct gaspRange {
      unsigned short maxPPEM; // Stored big-endian
      unsigned short behavior; // Stored big-endian
    };
    unsigned short numRanges = readShort(tableData + 2);
    if (tableSize < (UINT)4 + numRanges * 4) {
      aDWFace->ReleaseFontTable(tableContext);
      return true;
    }
    gaspRange *ranges = (gaspRange *)(tableData + 4);
    for (int i = 0; i < numRanges; i++) {
      if (readShort((char*)&ranges[i].maxPPEM) > ppem) {
        if (!(readShort((char*)&ranges[i].behavior) & GASP_DOGRAY)) {
          aDWFace->ReleaseFontTable(tableContext);
          return false;
        }
        break;
      }
    }
    aDWFace->ReleaseFontTable(tableContext);
  }
  return true;
}

static inline DWRITE_FONT_STRETCH
DWriteFontStretchFromStretch(int16_t aStretch)
{
    switch (aStretch) {
        case NS_FONT_STRETCH_ULTRA_CONDENSED:
            return DWRITE_FONT_STRETCH_ULTRA_CONDENSED;
        case NS_FONT_STRETCH_EXTRA_CONDENSED:
            return DWRITE_FONT_STRETCH_EXTRA_CONDENSED;
        case NS_FONT_STRETCH_CONDENSED:
            return DWRITE_FONT_STRETCH_CONDENSED;
        case NS_FONT_STRETCH_SEMI_CONDENSED:
            return DWRITE_FONT_STRETCH_SEMI_CONDENSED;
        case NS_FONT_STRETCH_NORMAL:
            return DWRITE_FONT_STRETCH_NORMAL;
        case NS_FONT_STRETCH_SEMI_EXPANDED:
            return DWRITE_FONT_STRETCH_SEMI_EXPANDED;
        case NS_FONT_STRETCH_EXPANDED:
            return DWRITE_FONT_STRETCH_EXPANDED;
        case NS_FONT_STRETCH_EXTRA_EXPANDED:
            return DWRITE_FONT_STRETCH_EXTRA_EXPANDED;
        case NS_FONT_STRETCH_ULTRA_EXPANDED:
            return DWRITE_FONT_STRETCH_ULTRA_EXPANDED;
        default:
            return DWRITE_FONT_STRETCH_UNDEFINED;
    }
}

ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace *aFontFace,
                                   const RefPtr<UnscaledFont>& aUnscaledFont,
                                   Float aSize,
                                   bool aUseEmbeddedBitmap,
                                   bool aForceGDIMode,
                                   IDWriteRenderingParams* aParams,
                                   Float aGamma,
                                   Float aContrast,
                                   const gfxFontStyle* aStyle)
    : ScaledFontBase(aUnscaledFont, aSize)
    , mFontFace(aFontFace)
    , mUseEmbeddedBitmap(aUseEmbeddedBitmap)
    , mForceGDIMode(aForceGDIMode)
    , mParams(aParams)
    , mGamma(aGamma)
    , mContrast(aContrast)
{
  if (aStyle) {
    mStyle = SkFontStyle(aStyle->weight,
                         DWriteFontStretchFromStretch(aStyle->stretch),
                         aStyle->style == NS_FONT_STYLE_NORMAL ?
                         SkFontStyle::kUpright_Slant : SkFontStyle::kItalic_Slant);
  }
}

already_AddRefed<Path>
ScaledFontDWrite::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget)
{
  if (aTarget->GetBackendType() != BackendType::DIRECT2D && aTarget->GetBackendType() != BackendType::DIRECT2D1_1) {
    return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
  }

  RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();

  PathBuilderD2D *pathBuilderD2D =
    static_cast<PathBuilderD2D*>(pathBuilder.get());

  CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink());

  return pathBuilder->Finish();
}


#ifdef USE_SKIA
SkTypeface*
ScaledFontDWrite::GetSkTypeface()
{
  if (!mTypeface) {
    RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
    if (!factory) {
      return nullptr;
    }

    Float gamma = mGamma;
    // Skia doesn't support a gamma value outside of 0-4, so default to 2.2
    if (gamma < 0.0f || gamma > 4.0f) {
      gamma = 2.2f;
    }

    Float contrast = mContrast;
    // Skia doesn't support a contrast value outside of 0-1, so default to 1.0
    if (contrast < 0.0f || contrast > 1.0f) {
      contrast = 1.0f;
    }

    mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode, gamma, contrast);
  }
  return mTypeface;
}
#endif

void
ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint)
{
  BackendType backendType = aBuilder->GetBackendType();
  if (backendType != BackendType::DIRECT2D && backendType != BackendType::DIRECT2D1_1) {
    ScaledFontBase::CopyGlyphsToBuilder(aBuffer, aBuilder, aTransformHint);
    return;
  }

  PathBuilderD2D *pathBuilderD2D =
    static_cast<PathBuilderD2D*>(aBuilder);

  if (pathBuilderD2D->IsFigureActive()) {
    gfxCriticalNote << "Attempting to copy glyphs to PathBuilderD2D with active figure.";
  }

  CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink());
}

void
ScaledFontDWrite::GetGlyphDesignMetrics(const uint16_t* aGlyphs,
                                        uint32_t aNumGlyphs,
                                        GlyphMetrics* aGlyphMetrics)
{
  DWRITE_FONT_METRICS fontMetrics;
  mFontFace->GetMetrics(&fontMetrics);

  vector<DWRITE_GLYPH_METRICS> metrics(aNumGlyphs);
  mFontFace->GetDesignGlyphMetrics(aGlyphs, aNumGlyphs, &metrics.front());

  Float scaleFactor = mSize / fontMetrics.designUnitsPerEm;

  for (uint32_t i = 0; i < aNumGlyphs; i++) {
    aGlyphMetrics[i].mXBearing = metrics[i].leftSideBearing * scaleFactor;
    aGlyphMetrics[i].mXAdvance = metrics[i].advanceWidth * scaleFactor;
    aGlyphMetrics[i].mYBearing = (metrics[i].topSideBearing -
                                  metrics[i].verticalOriginY) * scaleFactor;
    aGlyphMetrics[i].mYAdvance = metrics[i].advanceHeight * scaleFactor;
    aGlyphMetrics[i].mWidth = (metrics[i].advanceWidth -
                               metrics[i].leftSideBearing -
                               metrics[i].rightSideBearing) * scaleFactor;
    aGlyphMetrics[i].mHeight = (metrics[i].advanceHeight -
                                metrics[i].topSideBearing -
                                metrics[i].bottomSideBearing) * scaleFactor;
  }
}

void
ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer &aBuffer, ID2D1GeometrySink *aSink)
{
  std::vector<UINT16> indices;
  std::vector<FLOAT> advances;
  std::vector<DWRITE_GLYPH_OFFSET> offsets;
  indices.resize(aBuffer.mNumGlyphs);
  advances.resize(aBuffer.mNumGlyphs);
  offsets.resize(aBuffer.mNumGlyphs);

  memset(&advances.front(), 0, sizeof(FLOAT) * aBuffer.mNumGlyphs);
  for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
    indices[i] = aBuffer.mGlyphs[i].mIndex;
    offsets[i].advanceOffset = aBuffer.mGlyphs[i].mPosition.x;
    offsets[i].ascenderOffset = -aBuffer.mGlyphs[i].mPosition.y;
  }

  HRESULT hr =
    mFontFace->GetGlyphRunOutline(mSize, &indices.front(), &advances.front(),
                                  &offsets.front(), aBuffer.mNumGlyphs,
                                  FALSE, FALSE, aSink);
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to copy glyphs to geometry sink. Code: " << hexa(hr);
  }
}

bool
UnscaledFontDWrite::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton)
{
  UINT32 fileCount = 0;
  mFontFace->GetFiles(&fileCount, nullptr);

  if (fileCount > 1) {
    MOZ_ASSERT(false);
    return false;
  }

  if (!aDataCallback) {
    return true;
  }

  RefPtr<IDWriteFontFile> file;
  mFontFace->GetFiles(&fileCount, getter_AddRefs(file));

  const void *referenceKey;
  UINT32 refKeySize;
  // XXX - This can currently crash for webfonts, as when we get the reference
  // key out of the file, that can be an invalid reference key for the loader
  // we use it with. The fix to this is not obvious but it will probably
  // have to happen inside thebes.
  file->GetReferenceKey(&referenceKey, &refKeySize);

  RefPtr<IDWriteFontFileLoader> loader;
  file->GetLoader(getter_AddRefs(loader));

  RefPtr<IDWriteFontFileStream> stream;
  loader->CreateStreamFromKey(referenceKey, refKeySize, getter_AddRefs(stream));

  UINT64 fileSize64;
  stream->GetFileSize(&fileSize64);
  if (fileSize64 > UINT32_MAX) {
    MOZ_ASSERT(false);
    return false;
  }

  uint32_t fileSize = static_cast<uint32_t>(fileSize64);
  const void *fragmentStart;
  void *context;
  stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context);

  aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(), aBaton);

  stream->ReleaseFileFragment(context);

  return true;
}

static bool
GetDWriteName(RefPtr<IDWriteLocalizedStrings> aNames, std::vector<WCHAR>& aOutName)
{
  BOOL exists = false;
  UINT32 index = 0;
  HRESULT hr = aNames->FindLocaleName(L"en-us", &index, &exists);
  if (FAILED(hr)) {
    return false;
  }
  if (!exists) {
    // No english found, use whatever is first in the list.
    index = 0;
  }

  UINT32 length;
  hr = aNames->GetStringLength(index, &length);
  if (FAILED(hr)) {
    return false;
  }
  aOutName.resize(length + 1);
  hr = aNames->GetString(index, aOutName.data(), length + 1);
  return SUCCEEDED(hr);
}

static bool
GetDWriteFamilyName(const RefPtr<IDWriteFontFamily>& aFamily, std::vector<WCHAR>& aOutName)
{
  RefPtr<IDWriteLocalizedStrings> names;
  HRESULT hr = aFamily->GetFamilyNames(getter_AddRefs(names));
  if (FAILED(hr)) {
    return false;
  }
  return GetDWriteName(names, aOutName);
}

bool
UnscaledFontDWrite::GetWRFontDescriptor(WRFontDescriptorOutput aCb, void* aBaton)
{
  if (!mFont) {
    return false;
  }

  RefPtr<IDWriteFontFamily> family;
  HRESULT hr = mFont->GetFontFamily(getter_AddRefs(family));
  if (FAILED(hr)) {
    return false;
  }

  DWRITE_FONT_WEIGHT weight = mFont->GetWeight();
  DWRITE_FONT_STRETCH stretch = mFont->GetStretch();
  DWRITE_FONT_STYLE style = mFont->GetStyle();

  RefPtr<IDWriteFont> match;
  hr = family->GetFirstMatchingFont(weight, stretch, style, getter_AddRefs(match));
  if (FAILED(hr) ||
      match->GetWeight() != weight ||
      match->GetStretch() != stretch ||
      match->GetStyle() != style) {
    return false;
  }

  std::vector<WCHAR> familyName;
  if (!GetDWriteFamilyName(family, familyName)) {
    return false;
  }

  // The style information that identifies the font can be encoded easily in
  // less than 32 bits. Since the index is needed for font descriptors, only
  // the family name and style information, pass along the style in the index
  // data to avoid requiring a more complicated structure packing for it in
  // the data payload.
  uint32_t index = weight | (stretch << 16) | (style << 24);
  aCb(reinterpret_cast<const uint8_t*>(familyName.data()),
      (familyName.size() - 1) * sizeof(WCHAR),
      index, aBaton);
  return true;
}

bool
ScaledFontDWrite::GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton)
{
  InstanceData instance(this);
  aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance), nullptr, 0, aBaton);
  return true;
}

bool
ScaledFontDWrite::GetWRFontInstanceOptions(Maybe<wr::FontInstanceOptions>* aOutOptions,
                                           Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
                                           std::vector<FontVariation>* aOutVariations)
{
  wr::FontInstanceOptions options;
  options.render_mode = wr::ToFontRenderMode(GetDefaultAAMode());
  options.subpx_dir = wr::SubpixelDirection::Horizontal;
  options.flags = 0;
  if (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) {
    options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
  }
  if (UseEmbeddedBitmaps()) {
    options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
  }
  if (ForceGDIMode()) {
    options.flags |= wr::FontInstanceFlags::FORCE_GDI;
  }
  options.bg_color = wr::ToColorU(Color());
  *aOutOptions = Some(options);
  return true;
}

already_AddRefed<ScaledFont>
UnscaledFontDWrite::CreateScaledFont(Float aGlyphSize,
                                     const uint8_t* aInstanceData,
                                     uint32_t aInstanceDataLength,
                                     const FontVariation* aVariations,
                                     uint32_t aNumVariations)
{
  if (aInstanceDataLength < sizeof(ScaledFontDWrite::InstanceData)) {
    gfxWarning() << "DWrite scaled font instance data is truncated.";
    return nullptr;
  }

  const ScaledFontDWrite::InstanceData *instanceData =
    reinterpret_cast<const ScaledFontDWrite::InstanceData*>(aInstanceData);
  RefPtr<ScaledFontBase> scaledFont =
    new ScaledFontDWrite(mFontFace, this, aGlyphSize,
                         instanceData->mUseEmbeddedBitmap,
                         instanceData->mForceGDIMode,
                         nullptr,
                         instanceData->mGamma,
                         instanceData->mContrast);

  if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
    gfxWarning() << "Unable to create cairo scaled font DWrite font.";
    return nullptr;
  }

  return scaledFont.forget();
}

AntialiasMode
ScaledFontDWrite::GetDefaultAAMode()
{
  AntialiasMode defaultMode = GetSystemDefaultAAMode();

  if (defaultMode == AntialiasMode::GRAY) {
    if (!DoGrayscale(mFontFace, mSize)) {
      defaultMode = AntialiasMode::NONE;
    }
  }
  return defaultMode;
}

#ifdef USE_CAIRO_SCALED_FONT
cairo_font_face_t*
ScaledFontDWrite::GetCairoFontFace()
{
  if (!mFontFace) {
    return nullptr;
  }

  return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace);
}
#endif

}
}
