<commit_before>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* libcdr
 * Version: MPL 1.1 / GPLv2+ / LGPLv2+
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License or as specified alternatively below. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Major Contributor(s):
 * Copyright (C) 2012 <NAME> <<EMAIL>>
 *
 *
 * All Rights Reserved.
 *
 * For minor contributions see the git repository.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPLv2+"), or
 * the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
 * in which case the provisions of the GPLv2+ or the LGPLv2+ are applicable
 * instead of those above.
 */

#include <math.h>
#include "CDRCollector.h"
#include "CDRInternalStream.h"
#include "libcdr_utils.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#ifndef DUMP_IMAGE
#define DUMP_IMAGE 0
#endif

#ifndef DUMP_PATTERN
#define DUMP_PATTERN 0
#endif

namespace
{
int cdr_round(double d)
{
  return (d>0) ? int(d+0.5) : int(d-0.5);
}

void writeU16(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
  buffer.append((unsigned char)((value >> 8) & 0xFF));
}

void writeU32(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
  buffer.append((unsigned char)((value >> 8) & 0xFF));
  buffer.append((unsigned char)((value >> 16) & 0xFF));
  buffer.append((unsigned char)((value >> 24) & 0xFF));
}

void writeU8(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
}

#include "CDRColorProfiles.h"

}

libcdr::CDRCollector::CDRCollector(libwpg::WPGPaintInterface *painter) :
  m_painter(painter),
  m_isPageProperties(false),
  m_isPageStarted(false),
  m_pageOffsetX(-4.25), m_pageOffsetY(-5.5),
  m_pageWidth(8.5), m_pageHeight(11.0),
  m_currentFildId(0.0), m_currentOutlId(0),
  m_currentObjectLevel(0), m_currentPageLevel(0),
  m_currentImage(), m_currentPath(), m_currentTransform(), m_fillTransform(),
  m_fillStyles(), m_lineStyles(), m_polygon(0), m_bmps(), m_patterns(),
  m_isInPolygon(false), m_isInSpline(false), m_outputElements(), m_splineData(),
  m_fillOpacity(1.0), m_colorTransformCMYK2RGB(0), m_colorTransformLab2RGB(0)
{
  cmsHPROFILE tmpCMYKProfile = cmsOpenProfileFromMem(SWOP_icc, sizeof(SWOP_icc)/sizeof(SWOP_icc[0]));
  cmsHPROFILE tmpRGBProfile = cmsCreate_sRGBProfile();
  m_colorTransformCMYK2RGB = cmsCreateTransform(tmpCMYKProfile, TYPE_CMYK_DBL, tmpRGBProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
  cmsHPROFILE tmpLabProfile = cmsCreateLab4Profile(0);
  m_colorTransformLab2RGB = cmsCreateTransform(tmpLabProfile, TYPE_Lab_DBL, tmpRGBProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
  cmsCloseProfile(tmpLabProfile);
  cmsCloseProfile(tmpCMYKProfile);
  cmsCloseProfile(tmpRGBProfile);
}

libcdr::CDRCollector::~CDRCollector()
{
  if (m_isPageStarted)
    _endPage();
  if (m_colorTransformCMYK2RGB)
    cmsDeleteTransform(m_colorTransformCMYK2RGB);
  if (m_colorTransformLab2RGB)
    cmsDeleteTransform(m_colorTransformLab2RGB);
}

void libcdr::CDRCollector::_startPage(double width, double height)
{
  WPXPropertyList propList;
  propList.insert("svg:width", width);
  propList.insert("svg:height", height);
  if (m_painter)
  {
    m_painter->startGraphics(propList);
    m_isPageStarted = true;
  }
}

void libcdr::CDRCollector::_endPage()
{
  if (!m_isPageStarted)
    return;
  while (!m_outputElements.empty())
  {
    m_outputElements.top().draw(m_painter);
    m_outputElements.pop();
  }
  if (m_painter)
    m_painter->endGraphics();
  m_isPageStarted = false;
}

void libcdr::CDRCollector::collectPage(unsigned level)
{
  m_isPageProperties = true;
  m_currentPageLevel = level;
}

void libcdr::CDRCollector::collectObject(unsigned level)
{
  m_currentObjectLevel = level;
  m_currentFildId = 0;
  m_currentOutlId = 0;
}

void libcdr::CDRCollector::collectFlags(unsigned flags)
{
  if (!m_isPageProperties || (flags & 0x00ff0000))
  {
    m_isPageProperties = false;
  }
  else
  {
    m_isPageProperties = false;
    if (!m_isPageStarted)
      _startPage(m_pageWidth, m_pageHeight);
  }
}

void libcdr::CDRCollector::collectOtherList()
{
  m_isPageProperties = false;
}

void libcdr::CDRCollector::collectPageSize(double width, double height)
{
  m_pageWidth = width;
  m_pageHeight = height;
  m_pageOffsetX = -width / 2.0;
  m_pageOffsetY = -height / 2.0;
}

void libcdr::CDRCollector::collectCubicBezier(double x1, double y1, double x2, double y2, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectCubicBezier(%f, %f, %f, %f, %f, %f)\n", x1, y1, x2, y2, x, y));
  m_currentPath.appendCubicBezierTo(x1, y1, x2, y2, x, y);
}

void libcdr::CDRCollector::collectQuadraticBezier(double x1, double y1, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectQuadraticBezier(%f, %f, %f, %f)\n", x1, y1, x, y));
  m_currentPath.appendQuadraticBezierTo(x1, y1, x, y);
}

void libcdr::CDRCollector::collectMoveTo(double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectMoveTo(%f, %f)\n", x, y));
  m_currentPath.appendMoveTo(x,y);
}

void libcdr::CDRCollector::collectLineTo(double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectLineTo(%f, %f)\n", x, y));
  m_currentPath.appendLineTo(x, y);
}

void libcdr::CDRCollector::collectArcTo(double rx, double ry, bool largeArc, bool sweep, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectArcTo(%f, %f)\n", x, y));
  m_currentPath.appendArcTo(rx, ry, 0.0, largeArc, sweep, x, y);
}

void libcdr::CDRCollector::collectClosePath()
{
  CDR_DEBUG_MSG(("CDRCollector::collectClosePath\n"));
  m_currentPath.appendClosePath();
}

void libcdr::CDRCollector::_flushCurrentPath()
{
  CDR_DEBUG_MSG(("CDRCollector::collectFlushPath\n"));
  CDROutputElementList outputElement;
  if (!m_currentPath.empty() || (!m_splineData.empty() && m_isInSpline))
  {
    if (m_polygon && m_isInPolygon)
      m_polygon->create(m_currentPath);
    if (m_polygon)
    {
      delete m_polygon;
      m_polygon = 0;
    }
    m_isInPolygon = false;
    if (!m_splineData.empty() && m_isInSpline)
      m_splineData.create(m_currentPath);
    m_splineData.clear();
    m_isInSpline = false;
    bool firstPoint = true;
    double initialX = 0.0;
    double initialY = 0.0;
    double previousX = 0.0;
    double previousY = 0.0;
    double x = 0.0;
    double y = 0.0;
    WPXPropertyList style;
    WPXPropertyListVector gradient;
    _fillProperties(style, gradient);
    _lineProperties(style);
    outputElement.addStyle(style, gradient);
    m_currentPath.transform(m_currentTransform);
    CDRTransform tmpTrafo(1.0, 0.0, -m_pageOffsetX, 0.0, 1.0, -m_pageOffsetY);
    m_currentPath.transform(tmpTrafo);
    tmpTrafo = CDRTransform(1.0, 0.0, 0.0, 0.0, -1.0, m_pageHeight);
    m_currentPath.transform(tmpTrafo);
    WPXPropertyListVector tmpPath;
    m_currentPath.writeOut(tmpPath);
    WPXPropertyListVector path;
    WPXPropertyListVector::Iter i(tmpPath);
    bool ignoreM = false;
    for (i.rewind(); i.next();)
    {
      if (!i()["libwpg:path-action"])
        continue;
      if (i()["svg:x"] && i()["svg:y"])
      {
        x = i()["svg:x"]->getDouble();
        y = i()["svg:y"]->getDouble();
        if (firstPoint)
        {
          initialX = x;
          initialY = y;
          firstPoint = false;
        }
        else if (i()["libwpg:path-action"]->getStr() == "M")
        {
          if (CDR_ALMOST_ZERO(previousX - x) && CDR_ALMOST_ZERO(previousY - y))
            ignoreM = true;
          else
          {
            if ((CDR_ALMOST_ZERO(initialX - previousX) && CDR_ALMOST_ZERO(initialY - previousY)) || (style["draw:fill"] && style["draw:fill"]->getStr() != "none"))
            {
              WPXPropertyList node;
              node.insert("libwpg:path-action", "Z");
              path.append(node);
            }
            initialX = x;
            initialY = y;
          }
        }
      }
      previousX = x;
      previousY = y;
      if (!ignoreM)
        path.append(i());
      ignoreM = false;
    }
    if ((CDR_ALMOST_ZERO(initialX - previousX) && CDR_ALMOST_ZERO(initialY - previousY)) || (style["draw:fill"] && style["draw:fill"]->getStr() != "none"))
    {
      WPXPropertyList node;
      node.insert("libwpg:path-action", "Z");
      path.append(node);
    }

    outputElement.addPath(path);
    m_currentPath.clear();
  }
  if (m_currentImage.getImage().size())
  {
    double cx = m_currentImage.getMiddleX();
    double cy = m_currentImage.getMiddleY();
    m_currentTransform.applyToPoint(cx, cy);
    CDRTransform tmpTrafo(1.0, 0.0, -m_pageOffsetX, 0.0, 1.0, -m_pageOffsetY);
    tmpTrafo.applyToPoint(cx, cy);
    tmpTrafo = CDRTransform(1.0, 0.0, 0.0, 0.0, -1.0, m_pageHeight);
    tmpTrafo.applyToPoint(cx, cy);
    double scaleX = (m_currentTransform.m_v0 < 0.0 ? -1.0 : 1.0)*sqrt(m_currentTransform.m_v0 * m_currentTransform.m_v0 + m_currentTransform.m_v1 * m_currentTransform.m_v1);
    double scaleY = (m_currentTransform.m_v3 < 0.0 ? -1.0 : 1.0)*sqrt(m_currentTransform.m_v3 * m_currentTransform.m_v3 + m_currentTransform.m_v4 * m_currentTransform.m_v4);
    bool flipX(scaleX < 0);
    bool flipY(scaleY < 0);
    double width = fabs(scaleX)*m_currentImage.getWidth();
    double height = fabs(scaleY)*m_currentImage.getHeight();
    double rotate = -atan2(m_currentTransform.m_v3, m_currentTransform.m_v4);

    WPXPropertyList propList;

    propList.insert("svg:x", cx - width / 2.0);
    propList.insert("svg:width", width);
    propList.insert("svg:y", cy - height / 2.0);
    propList.insert("svg:height", height);

    if (flipX)
    {
      propList.insert("draw:mirror-horizontal", true);
      rotate = M_PI - rotate;
    }
    if (flipY)
    {
      propList.insert("draw:mirror-vertical", true);
      rotate *= -1.0;
    }

    while (rotate < 0.0)
      rotate += 2.0*M_PI;
    while (rotate > 2.0*M_PI)
      rotate -= 2.0*M_PI;

    if (rotate != 0.0)
      propList.insert("libwpg:rotate", rotate * 180 / M_PI, WPX_GENERIC);

    propList.insert("libwpg:mime-type", "image/bmp");

    outputElement.addGraphicObject(propList, m_currentImage.getImage());
  }
  m_currentImage = libcdr::CDRImage();
  if (!outputElement.empty())
    m_outputElements.push(outputElement);
  m_currentTransform = libcdr::CDRTransform();
  m_fillTransform = libcdr::CDRTransform();
  m_fillOpacity = 1.0;
}

void libcdr::CDRCollector::collectTransform(double v0, double v1, double x0, double v3, double v4, double y0)
{
  m_currentTransform = libcdr::CDRTransform(v0, v1, x0, v3, v4, y0);
}

void libcdr::CDRCollector::collectFillTransform(double v0, double v1, double x0, double v3, double v4, double y0)
{
  m_fillTransform = libcdr::CDRTransform(v0, v1, x0, v3, v4, y0);
}

void libcdr::CDRCollector::collectLevel(unsigned level)
{
  if (level <= m_currentObjectLevel)
  {
    _flushCurrentPath();
    m_currentObjectLevel = 0;
  }
  if (level <= m_currentPageLevel)
  {
    _endPage();
    m_currentPageLevel = 0;
  }
}

void libcdr::CDRCollector::collectFildId(unsigned id)
{
  m_currentFildId = id;
}

void libcdr::CDRCollector::collectOutlId(unsigned id)
{
  m_currentOutlId = id;
}

void libcdr::CDRCollector::collectFild(unsigned id, unsigned short fillType, const libcdr::CDRColor &color1, const libcdr::CDRColor &color2,
                                       const libcdr::CDRGradient &gradient, const libcdr::CDRImageFill &imageFill)
{
  m_fillStyles[id] = CDRFillStyle(fillType, color1, color2, gradient, imageFill);
}

void libcdr::CDRCollector::collectOutl(unsigned id, unsigned short lineType, unsigned short capsType, unsigned short joinType, double lineWidth,
                                       double stretch, double angle, const CDRColor &color, const std::vector<unsigned short> &dashArray,
                                       unsigned startMarkerId, unsigned endMarkerId)
{
  m_lineStyles[id] = CDRLineStyle(lineType, capsType, joinType, lineWidth, stretch, angle, color, dashArray, startMarkerId, endMarkerId);
}

void libcdr::CDRCollector::collectRotate(double /* angle */)
{
}

void libcdr::CDRCollector::collectPolygon()
{
  m_isInPolygon = true;
}

void libcdr::CDRCollector::collectSpline()
{
  m_isInSpline = true;
}

void libcdr::CDRCollector::collectPolygonTransform(unsigned numAngles, unsigned nextPoint, double rx, double ry, double cx, double cy)
{
  if (m_polygon)
    delete m_polygon;
  m_polygon = new CDRPolygon(numAngles, nextPoint, rx, ry, cx, cy);
}

unsigned libcdr::CDRCollector::_getBMPColor(const CDRColor &color)
{
  switch (color.m_colorModel)
  {
  case 0:
    return _getRGBColor(libcdr::CDRColor(0, color.m_colorValue));
  case 1:
    return _getRGBColor(libcdr::CDRColor(5, color.m_colorValue));
  case 2:
    return _getRGBColor(libcdr::CDRColor(4, color.m_colorValue));
  case 3:
    return _getRGBColor(libcdr::CDRColor(3, color.m_colorValue));
  case 4:
    return _getRGBColor(libcdr::CDRColor(6, color.m_colorValue));
  case 5:
    return _getRGBColor(libcdr::CDRColor(9, color.m_colorValue));
  case 6:
    return _getRGBColor(libcdr::CDRColor(8, color.m_colorValue));
  case 7:
    return _getRGBColor(libcdr::CDRColor(7, color.m_colorValue));
  case 8:
    return color.m_colorValue;
  case 9:
    return color.m_colorValue;
  case 10:
    return _getRGBColor(libcdr::CDRColor(5, color.m_colorValue));
  case 11:
    return _getRGBColor(libcdr::CDRColor(18, color.m_colorValue));
  default:
    return color.m_colorValue;
  }
}

unsigned libcdr::CDRCollector::_getRGBColor(const CDRColor &color)
{
  unsigned char red = 0;
  unsigned char green = 0;
  unsigned char blue = 0;
  unsigned char col0 = color.m_colorValue & 0xff;
  unsigned char col1 = (color.m_colorValue & 0xff00) >> 8;
  unsigned char col2 = (color.m_colorValue & 0xff0000) >> 16;
  unsigned char col3 = (color.m_colorValue & 0xff000000) >> 24;
  if (color.m_colorModel == 0x02) // CMYK100
  {
    double cmyk[4] =
    {
      (double)col0,
      (double)col1,
      (double)col2,
      (double)col3
    };
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformCMYK2RGB, cmyk, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x03 || color.m_colorModel == 0x01 || color.m_colorModel == 0x11) // CMYK255
  {
    double cmyk[4] =
    {
      (double)col0*100.0/255.0,
      (double)col1*100.0/255.0,
      (double)col2*100.0/255.0,
      (double)col3*100.0/255.0
    };
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformCMYK2RGB, cmyk, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x04) // CMY
  {
    red = 255 - col0;
    green = 255 - col1;
    blue = 255 - col2;
  }
  else if (color.m_colorModel == 0x05) // RGB
  {
    red = col2;
    green = col1;
    blue = col0;
  }
  else if (color.m_colorModel == 0x06) // HSB
  {
    unsigned short hue = (col1<<8) | col0;
    double saturation = (double)col2/255.0;
    double brightness = (double)col3/255.0;

    while (hue > 360)
      hue -= 360;

    double satRed, satGreen, satBlue;

    if (hue < 120)
    {
      satRed = (double)(120 - hue) / 60.0;
      satGreen = (double)hue / 60.0;
      satBlue = 0;
    }
    else if (hue < 240)
    {
      satRed = 0;
      satGreen = (double)(240 - hue) / 60.0;
      satBlue = (double)(hue - 120) / 60.0;
    }
    else
    {
      satRed = (double)(hue - 240) / 60.0;
      satGreen = 0.0;
      satBlue = (double)(360 - hue) / 60.0;
    }
    red = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satRed > 1 ? 1 : satRed)) * brightness);
    green = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satGreen > 1 ? 1 : satGreen)) * brightness);
    blue = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satBlue > 1 ? 1 : satBlue)) * brightness);
  }
  else if (color.m_colorModel == 0x07) // HLS
  {
    unsigned short hue = (col1<<8) | col0;
    double lightness = (double)col2/255.0;
    double saturation = (double)col3/255.0;

    while (hue > 360)
      hue -= 360;

    double satRed, satGreen, satBlue;

    if (hue < 120)
    {
      satRed =  (double)(120 - hue) / 60.0;
      satGreen = (double)hue/60.0;
      satBlue = 0.0;
    }
    else if (hue < 240)
    {
      satRed = 0;
      satGreen = (double)(240 - hue) / 60.0;
      satBlue = (double)(hue - 120) / 60.0;
    }
    else
    {
      satRed = (double)(hue - 240) / 60.0;
      satGreen = 0;
      satBlue = (double)(360 - hue) / 60.0;
    }

    double tmpRed = 2*saturation*(satRed > 1 ? 1 : satRed) + 1 - saturation;
    double tmpGreen = 2*saturation*(satGreen > 1 ? 1 : satGreen) + 1 - saturation;
    double tmpBlue = 2*saturation*(satBlue > 1 ? 1 : satBlue) + 1 - saturation;

    if (lightness < 0.5)
    {
      red = (unsigned char)cdr_round(255.0*lightness*tmpRed);
      green = (unsigned char)cdr_round(255.0*lightness*tmpGreen);
      blue = (unsigned char)cdr_round(255.0*lightness*tmpBlue);
    }
    else
    {
      red = (unsigned char)cdr_round(255*((1 - lightness) * tmpRed + 2 * lightness - 1));
      green = (unsigned char)cdr_round(255*((1 - lightness) * tmpGreen + 2 * lightness - 1));
      blue = (unsigned char)cdr_round(255*((1 - lightness) * tmpBlue + 2 * lightness - 1));
    }
  }
  else if (color.m_colorModel == 0x09) // Grayscale
  {
    red = col0;
    green = col0;
    blue = col0;
  }
  else if (color.m_colorModel == 0x0c) // Lab
  {
    cmsCIELab Lab;
    Lab.L = (double)col0*100.0/255.0;
    Lab.a = (double)(signed char)col1;
    Lab.b = (double)(signed char)col2;
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformLab2RGB, &Lab, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x12) // Lab
  {
    cmsCIELab Lab;
    Lab.L = (double)col0*100.0/255.0;
    Lab.a = (double)((signed char)(col1 - 0x80));
    Lab.b = (double)((signed char)(col2 - 0x80));
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformLab2RGB, &Lab, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x19) // HKS
  {
    unsigned char HKS_red [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned char HKS_green [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned char HKS_blue [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned short hks = (unsigned short)(color.m_colorValue & 0xffff)+85;
    unsigned hksIndex = hks % 86;
    hks /= 86;
    unsigned blackPercent = hks/10;
    switch (blackPercent)
    {
    case 2:
      blackPercent = 10;
      break;
    case 3:
      blackPercent = 30;
      break;
    case 4:
      blackPercent = 50;
      break;
    default:
      blackPercent = 0;
      break;
    }
    unsigned colorPercent = (hks % 10) ? (hks % 10) * 10 : 100;
    // try to avoid overflow due to rounding issues, so use first wider integer types and later, fill the unsigned char properly
    unsigned tmpRed = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_red[hksIndex]*(double)colorPercent/100.0));
    unsigned tmpGreen = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_green[hksIndex]*(double)colorPercent/100.0));
    unsigned tmpBlue = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_blue[hksIndex]*(double)colorPercent/100.0));
    red = (tmpRed < 255 ? (unsigned char)tmpRed : 255);
    green = (tmpGreen < 255 ? (unsigned char)tmpGreen : 255);
    blue = (tmpBlue < 255 ? (unsigned char)tmpBlue : 255);

  }
  return (unsigned)((red << 16) | (green << 8) | blue);
}

WPXString libcdr::CDRCollector::_getRGBColorString(const libcdr::CDRColor &color)
{
  WPXString tempString;
  tempString.sprintf("#%.6x", _getRGBColor(color));
  return tempString;
}

void libcdr::CDRCollector::_fillProperties(WPXPropertyList &propList, WPXPropertyListVector & /* vec */)
{
  if (m_fillOpacity < 1.0)
    propList.insert("draw:opacity", m_fillOpacity, WPX_PERCENT);
  if (m_currentFildId == 0)
    propList.insert("draw:fill", "none");
  else
  {
    std::map<unsigned, CDRFillStyle>::iterator iter = m_fillStyles.find(m_currentFildId);
    if (iter == m_fillStyles.end())
      propList.insert("draw:fill", "none");
    else
    {
      switch (iter->second.fillType)
      {
      case 1: // Solid
        propList.insert("draw:fill", "solid");
        propList.insert("draw:fill-color", _getRGBColorString(iter->second.color1));
        propList.insert("svg:fill-rule", "evenodd");
        break;
      case 2: // Gradient
        if (iter->second.gradient.m_stops.empty())
          propList.insert("draw:fill", "none");
        else if (iter->second.gradient.m_stops.size() == 1)
        {
          propList.insert("draw:fill", "solid");
          propList.insert("draw:fill-color", _getRGBColorString(iter->second.gradient.m_stops[0].m_color));
          propList.insert("svg:fill-rule", "evenodd");
        }
        else if (iter->second.gradient.m_stops.size() == 2)
        {
          double angle = iter->second.gradient.m_angle;
          while (angle < 0.0)
            angle += 360.0;
          while (angle > 360.0)
            angle -= 360.0;
          propList.insert("draw:fill", "gradient");
          propList.insert("draw:start-color", _getRGBColorString(iter->second.gradient.m_stops[0].m_color));
          propList.insert("draw:end-color", _getRGBColorString(iter->second.gradient.m_stops[1].m_color));
          propList.insert("draw:angle", (int)angle);
          switch (iter->second.gradient.m_type)
          {
          case 1: // linear
          case 3: // conical
            propList.insert("draw:style", "linear");
            angle += 90.0;
            while (angle < 0.0)
              angle += 360.0;
            while (angle > 360.0)
              angle -= 360.0;
            propList.insert("draw:angle", (int)angle);
            propList.insert("draw:border", (double)(iter->second.gradient.m_edgeOffset)/100.0, WPX_PERCENT);
            break;
          case 2: // radial
            propList.insert("draw:border", (2.0 * (double)(iter->second.gradient.m_edgeOffset)/100.0), WPX_PERCENT);
            propList.insert("draw:style", "radial");
            propList.insert("svg:cx", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            propList.insert("svg:cy", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            break;
          case 4: // square
            propList.insert("draw:border", (2.0 * (double)(iter->second.gradient.m_edgeOffset)/100.0), WPX_PERCENT);
            propList.insert("draw:style", "square");
            propList.insert("svg:cx", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            propList.insert("svg:cy", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            break;
          default:
            propList.insert("draw:fill", "none");
            break;
          }
        }
        else // output svg gradient as a hail mary pass towards ODG that does not really support it
        {
        }
        break;
      case 7: // Pattern
      {
        std::map<unsigned, CDRPattern>::iterator iterPattern = m_patterns.find(iter->second.imageFill.id);
        if (iterPattern != m_patterns.end())
        {
          propList.insert("draw:fill", "bitmap");
          WPXBinaryData image;
          _generateBitmapFromPattern(image, iterPattern->second, iter->second.color1, iter->second.color2);
#if DUMP_PATTERN
          WPXString filename;
          filename.sprintf("pattern%.8x.bmp", iter->second.imageFill.id);
          FILE *f = fopen(filename.cstr(), "wb");
          if (f)
          {
            const unsigned char *tmpBuffer = image.getDataBuffer();<commit_msg>Some gradient improvement<commit_after>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* libcdr
 * Version: MPL 1.1 / GPLv2+ / LGPLv2+
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License or as specified alternatively below. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Major Contributor(s):
 * Copyright (C) 2012 <NAME> <<EMAIL>>
 *
 *
 * All Rights Reserved.
 *
 * For minor contributions see the git repository.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPLv2+"), or
 * the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
 * in which case the provisions of the GPLv2+ or the LGPLv2+ are applicable
 * instead of those above.
 */

#include <math.h>
#include "CDRCollector.h"
#include "CDRInternalStream.h"
#include "libcdr_utils.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#ifndef DUMP_IMAGE
#define DUMP_IMAGE 0
#endif

#ifndef DUMP_PATTERN
#define DUMP_PATTERN 0
#endif

namespace
{
int cdr_round(double d)
{
  return (d>0) ? int(d+0.5) : int(d-0.5);
}

void writeU16(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
  buffer.append((unsigned char)((value >> 8) & 0xFF));
}

void writeU32(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
  buffer.append((unsigned char)((value >> 8) & 0xFF));
  buffer.append((unsigned char)((value >> 16) & 0xFF));
  buffer.append((unsigned char)((value >> 24) & 0xFF));
}

void writeU8(WPXBinaryData &buffer, const int value)
{
  buffer.append((unsigned char)(value & 0xFF));
}

#include "CDRColorProfiles.h"

}

libcdr::CDRCollector::CDRCollector(libwpg::WPGPaintInterface *painter) :
  m_painter(painter),
  m_isPageProperties(false),
  m_isPageStarted(false),
  m_pageOffsetX(-4.25), m_pageOffsetY(-5.5),
  m_pageWidth(8.5), m_pageHeight(11.0),
  m_currentFildId(0.0), m_currentOutlId(0),
  m_currentObjectLevel(0), m_currentPageLevel(0),
  m_currentImage(), m_currentPath(), m_currentTransform(), m_fillTransform(),
  m_fillStyles(), m_lineStyles(), m_polygon(0), m_bmps(), m_patterns(),
  m_isInPolygon(false), m_isInSpline(false), m_outputElements(), m_splineData(),
  m_fillOpacity(1.0), m_colorTransformCMYK2RGB(0), m_colorTransformLab2RGB(0)
{
  cmsHPROFILE tmpCMYKProfile = cmsOpenProfileFromMem(SWOP_icc, sizeof(SWOP_icc)/sizeof(SWOP_icc[0]));
  cmsHPROFILE tmpRGBProfile = cmsCreate_sRGBProfile();
  m_colorTransformCMYK2RGB = cmsCreateTransform(tmpCMYKProfile, TYPE_CMYK_DBL, tmpRGBProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
  cmsHPROFILE tmpLabProfile = cmsCreateLab4Profile(0);
  m_colorTransformLab2RGB = cmsCreateTransform(tmpLabProfile, TYPE_Lab_DBL, tmpRGBProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
  cmsCloseProfile(tmpLabProfile);
  cmsCloseProfile(tmpCMYKProfile);
  cmsCloseProfile(tmpRGBProfile);
}

libcdr::CDRCollector::~CDRCollector()
{
  if (m_isPageStarted)
    _endPage();
  if (m_colorTransformCMYK2RGB)
    cmsDeleteTransform(m_colorTransformCMYK2RGB);
  if (m_colorTransformLab2RGB)
    cmsDeleteTransform(m_colorTransformLab2RGB);
}

void libcdr::CDRCollector::_startPage(double width, double height)
{
  WPXPropertyList propList;
  propList.insert("svg:width", width);
  propList.insert("svg:height", height);
  if (m_painter)
  {
    m_painter->startGraphics(propList);
    m_isPageStarted = true;
  }
}

void libcdr::CDRCollector::_endPage()
{
  if (!m_isPageStarted)
    return;
  while (!m_outputElements.empty())
  {
    m_outputElements.top().draw(m_painter);
    m_outputElements.pop();
  }
  if (m_painter)
    m_painter->endGraphics();
  m_isPageStarted = false;
}

void libcdr::CDRCollector::collectPage(unsigned level)
{
  m_isPageProperties = true;
  m_currentPageLevel = level;
}

void libcdr::CDRCollector::collectObject(unsigned level)
{
  m_currentObjectLevel = level;
  m_currentFildId = 0;
  m_currentOutlId = 0;
}

void libcdr::CDRCollector::collectFlags(unsigned flags)
{
  if (!m_isPageProperties || (flags & 0x00ff0000))
  {
    m_isPageProperties = false;
  }
  else
  {
    m_isPageProperties = false;
    if (!m_isPageStarted)
      _startPage(m_pageWidth, m_pageHeight);
  }
}

void libcdr::CDRCollector::collectOtherList()
{
  m_isPageProperties = false;
}

void libcdr::CDRCollector::collectPageSize(double width, double height)
{
  m_pageWidth = width;
  m_pageHeight = height;
  m_pageOffsetX = -width / 2.0;
  m_pageOffsetY = -height / 2.0;
}

void libcdr::CDRCollector::collectCubicBezier(double x1, double y1, double x2, double y2, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectCubicBezier(%f, %f, %f, %f, %f, %f)\n", x1, y1, x2, y2, x, y));
  m_currentPath.appendCubicBezierTo(x1, y1, x2, y2, x, y);
}

void libcdr::CDRCollector::collectQuadraticBezier(double x1, double y1, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectQuadraticBezier(%f, %f, %f, %f)\n", x1, y1, x, y));
  m_currentPath.appendQuadraticBezierTo(x1, y1, x, y);
}

void libcdr::CDRCollector::collectMoveTo(double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectMoveTo(%f, %f)\n", x, y));
  m_currentPath.appendMoveTo(x,y);
}

void libcdr::CDRCollector::collectLineTo(double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectLineTo(%f, %f)\n", x, y));
  m_currentPath.appendLineTo(x, y);
}

void libcdr::CDRCollector::collectArcTo(double rx, double ry, bool largeArc, bool sweep, double x, double y)
{
  CDR_DEBUG_MSG(("CDRCollector::collectArcTo(%f, %f)\n", x, y));
  m_currentPath.appendArcTo(rx, ry, 0.0, largeArc, sweep, x, y);
}

void libcdr::CDRCollector::collectClosePath()
{
  CDR_DEBUG_MSG(("CDRCollector::collectClosePath\n"));
  m_currentPath.appendClosePath();
}

void libcdr::CDRCollector::_flushCurrentPath()
{
  CDR_DEBUG_MSG(("CDRCollector::collectFlushPath\n"));
  CDROutputElementList outputElement;
  if (!m_currentPath.empty() || (!m_splineData.empty() && m_isInSpline))
  {
    if (m_polygon && m_isInPolygon)
      m_polygon->create(m_currentPath);
    if (m_polygon)
    {
      delete m_polygon;
      m_polygon = 0;
    }
    m_isInPolygon = false;
    if (!m_splineData.empty() && m_isInSpline)
      m_splineData.create(m_currentPath);
    m_splineData.clear();
    m_isInSpline = false;
    bool firstPoint = true;
    double initialX = 0.0;
    double initialY = 0.0;
    double previousX = 0.0;
    double previousY = 0.0;
    double x = 0.0;
    double y = 0.0;
    WPXPropertyList style;
    WPXPropertyListVector gradient;
    _fillProperties(style, gradient);
    _lineProperties(style);
    outputElement.addStyle(style, gradient);
    m_currentPath.transform(m_currentTransform);
    CDRTransform tmpTrafo(1.0, 0.0, -m_pageOffsetX, 0.0, 1.0, -m_pageOffsetY);
    m_currentPath.transform(tmpTrafo);
    tmpTrafo = CDRTransform(1.0, 0.0, 0.0, 0.0, -1.0, m_pageHeight);
    m_currentPath.transform(tmpTrafo);
    WPXPropertyListVector tmpPath;
    m_currentPath.writeOut(tmpPath);
    WPXPropertyListVector path;
    WPXPropertyListVector::Iter i(tmpPath);
    bool ignoreM = false;
    for (i.rewind(); i.next();)
    {
      if (!i()["libwpg:path-action"])
        continue;
      if (i()["svg:x"] && i()["svg:y"])
      {
        x = i()["svg:x"]->getDouble();
        y = i()["svg:y"]->getDouble();
        if (firstPoint)
        {
          initialX = x;
          initialY = y;
          firstPoint = false;
        }
        else if (i()["libwpg:path-action"]->getStr() == "M")
        {
          if (CDR_ALMOST_ZERO(previousX - x) && CDR_ALMOST_ZERO(previousY - y))
            ignoreM = true;
          else
          {
            if ((CDR_ALMOST_ZERO(initialX - previousX) && CDR_ALMOST_ZERO(initialY - previousY)) || (style["draw:fill"] && style["draw:fill"]->getStr() != "none"))
            {
              WPXPropertyList node;
              node.insert("libwpg:path-action", "Z");
              path.append(node);
            }
            initialX = x;
            initialY = y;
          }
        }
      }
      previousX = x;
      previousY = y;
      if (!ignoreM)
        path.append(i());
      ignoreM = false;
    }
    if ((CDR_ALMOST_ZERO(initialX - previousX) && CDR_ALMOST_ZERO(initialY - previousY)) || (style["draw:fill"] && style["draw:fill"]->getStr() != "none"))
    {
      WPXPropertyList node;
      node.insert("libwpg:path-action", "Z");
      path.append(node);
    }

    outputElement.addPath(path);
    m_currentPath.clear();
  }
  if (m_currentImage.getImage().size())
  {
    double cx = m_currentImage.getMiddleX();
    double cy = m_currentImage.getMiddleY();
    m_currentTransform.applyToPoint(cx, cy);
    CDRTransform tmpTrafo(1.0, 0.0, -m_pageOffsetX, 0.0, 1.0, -m_pageOffsetY);
    tmpTrafo.applyToPoint(cx, cy);
    tmpTrafo = CDRTransform(1.0, 0.0, 0.0, 0.0, -1.0, m_pageHeight);
    tmpTrafo.applyToPoint(cx, cy);
    double scaleX = (m_currentTransform.m_v0 < 0.0 ? -1.0 : 1.0)*sqrt(m_currentTransform.m_v0 * m_currentTransform.m_v0 + m_currentTransform.m_v1 * m_currentTransform.m_v1);
    double scaleY = (m_currentTransform.m_v3 < 0.0 ? -1.0 : 1.0)*sqrt(m_currentTransform.m_v3 * m_currentTransform.m_v3 + m_currentTransform.m_v4 * m_currentTransform.m_v4);
    bool flipX(scaleX < 0);
    bool flipY(scaleY < 0);
    double width = fabs(scaleX)*m_currentImage.getWidth();
    double height = fabs(scaleY)*m_currentImage.getHeight();
    double rotate = -atan2(m_currentTransform.m_v3, m_currentTransform.m_v4);

    WPXPropertyList propList;

    propList.insert("svg:x", cx - width / 2.0);
    propList.insert("svg:width", width);
    propList.insert("svg:y", cy - height / 2.0);
    propList.insert("svg:height", height);

    if (flipX)
    {
      propList.insert("draw:mirror-horizontal", true);
      rotate = M_PI - rotate;
    }
    if (flipY)
    {
      propList.insert("draw:mirror-vertical", true);
      rotate *= -1.0;
    }

    while (rotate < 0.0)
      rotate += 2.0*M_PI;
    while (rotate > 2.0*M_PI)
      rotate -= 2.0*M_PI;

    if (rotate != 0.0)
      propList.insert("libwpg:rotate", rotate * 180 / M_PI, WPX_GENERIC);

    propList.insert("libwpg:mime-type", "image/bmp");

    outputElement.addGraphicObject(propList, m_currentImage.getImage());
  }
  m_currentImage = libcdr::CDRImage();
  if (!outputElement.empty())
    m_outputElements.push(outputElement);
  m_currentTransform = libcdr::CDRTransform();
  m_fillTransform = libcdr::CDRTransform();
  m_fillOpacity = 1.0;
}

void libcdr::CDRCollector::collectTransform(double v0, double v1, double x0, double v3, double v4, double y0)
{
  m_currentTransform = libcdr::CDRTransform(v0, v1, x0, v3, v4, y0);
}

void libcdr::CDRCollector::collectFillTransform(double v0, double v1, double x0, double v3, double v4, double y0)
{
  m_fillTransform = libcdr::CDRTransform(v0, v1, x0, v3, v4, y0);
}

void libcdr::CDRCollector::collectLevel(unsigned level)
{
  if (level <= m_currentObjectLevel)
  {
    _flushCurrentPath();
    m_currentObjectLevel = 0;
  }
  if (level <= m_currentPageLevel)
  {
    _endPage();
    m_currentPageLevel = 0;
  }
}

void libcdr::CDRCollector::collectFildId(unsigned id)
{
  m_currentFildId = id;
}

void libcdr::CDRCollector::collectOutlId(unsigned id)
{
  m_currentOutlId = id;
}

void libcdr::CDRCollector::collectFild(unsigned id, unsigned short fillType, const libcdr::CDRColor &color1, const libcdr::CDRColor &color2,
                                       const libcdr::CDRGradient &gradient, const libcdr::CDRImageFill &imageFill)
{
  m_fillStyles[id] = CDRFillStyle(fillType, color1, color2, gradient, imageFill);
}

void libcdr::CDRCollector::collectOutl(unsigned id, unsigned short lineType, unsigned short capsType, unsigned short joinType, double lineWidth,
                                       double stretch, double angle, const CDRColor &color, const std::vector<unsigned short> &dashArray,
                                       unsigned startMarkerId, unsigned endMarkerId)
{
  m_lineStyles[id] = CDRLineStyle(lineType, capsType, joinType, lineWidth, stretch, angle, color, dashArray, startMarkerId, endMarkerId);
}

void libcdr::CDRCollector::collectRotate(double /* angle */)
{
}

void libcdr::CDRCollector::collectPolygon()
{
  m_isInPolygon = true;
}

void libcdr::CDRCollector::collectSpline()
{
  m_isInSpline = true;
}

void libcdr::CDRCollector::collectPolygonTransform(unsigned numAngles, unsigned nextPoint, double rx, double ry, double cx, double cy)
{
  if (m_polygon)
    delete m_polygon;
  m_polygon = new CDRPolygon(numAngles, nextPoint, rx, ry, cx, cy);
}

unsigned libcdr::CDRCollector::_getBMPColor(const CDRColor &color)
{
  switch (color.m_colorModel)
  {
  case 0:
    return _getRGBColor(libcdr::CDRColor(0, color.m_colorValue));
  case 1:
    return _getRGBColor(libcdr::CDRColor(5, color.m_colorValue));
  case 2:
    return _getRGBColor(libcdr::CDRColor(4, color.m_colorValue));
  case 3:
    return _getRGBColor(libcdr::CDRColor(3, color.m_colorValue));
  case 4:
    return _getRGBColor(libcdr::CDRColor(6, color.m_colorValue));
  case 5:
    return _getRGBColor(libcdr::CDRColor(9, color.m_colorValue));
  case 6:
    return _getRGBColor(libcdr::CDRColor(8, color.m_colorValue));
  case 7:
    return _getRGBColor(libcdr::CDRColor(7, color.m_colorValue));
  case 8:
    return color.m_colorValue;
  case 9:
    return color.m_colorValue;
  case 10:
    return _getRGBColor(libcdr::CDRColor(5, color.m_colorValue));
  case 11:
    return _getRGBColor(libcdr::CDRColor(18, color.m_colorValue));
  default:
    return color.m_colorValue;
  }
}

unsigned libcdr::CDRCollector::_getRGBColor(const CDRColor &color)
{
  unsigned char red = 0;
  unsigned char green = 0;
  unsigned char blue = 0;
  unsigned char col0 = color.m_colorValue & 0xff;
  unsigned char col1 = (color.m_colorValue & 0xff00) >> 8;
  unsigned char col2 = (color.m_colorValue & 0xff0000) >> 16;
  unsigned char col3 = (color.m_colorValue & 0xff000000) >> 24;
  if (color.m_colorModel == 0x02) // CMYK100
  {
    double cmyk[4] =
    {
      (double)col0,
      (double)col1,
      (double)col2,
      (double)col3
    };
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformCMYK2RGB, cmyk, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x03 || color.m_colorModel == 0x01 || color.m_colorModel == 0x11) // CMYK255
  {
    double cmyk[4] =
    {
      (double)col0*100.0/255.0,
      (double)col1*100.0/255.0,
      (double)col2*100.0/255.0,
      (double)col3*100.0/255.0
    };
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformCMYK2RGB, cmyk, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x04) // CMY
  {
    red = 255 - col0;
    green = 255 - col1;
    blue = 255 - col2;
  }
  else if (color.m_colorModel == 0x05) // RGB
  {
    red = col2;
    green = col1;
    blue = col0;
  }
  else if (color.m_colorModel == 0x06) // HSB
  {
    unsigned short hue = (col1<<8) | col0;
    double saturation = (double)col2/255.0;
    double brightness = (double)col3/255.0;

    while (hue > 360)
      hue -= 360;

    double satRed, satGreen, satBlue;

    if (hue < 120)
    {
      satRed = (double)(120 - hue) / 60.0;
      satGreen = (double)hue / 60.0;
      satBlue = 0;
    }
    else if (hue < 240)
    {
      satRed = 0;
      satGreen = (double)(240 - hue) / 60.0;
      satBlue = (double)(hue - 120) / 60.0;
    }
    else
    {
      satRed = (double)(hue - 240) / 60.0;
      satGreen = 0.0;
      satBlue = (double)(360 - hue) / 60.0;
    }
    red = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satRed > 1 ? 1 : satRed)) * brightness);
    green = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satGreen > 1 ? 1 : satGreen)) * brightness);
    blue = (unsigned char)cdr_round(255*(1 - saturation + saturation * (satBlue > 1 ? 1 : satBlue)) * brightness);
  }
  else if (color.m_colorModel == 0x07) // HLS
  {
    unsigned short hue = (col1<<8) | col0;
    double lightness = (double)col2/255.0;
    double saturation = (double)col3/255.0;

    while (hue > 360)
      hue -= 360;

    double satRed, satGreen, satBlue;

    if (hue < 120)
    {
      satRed =  (double)(120 - hue) / 60.0;
      satGreen = (double)hue/60.0;
      satBlue = 0.0;
    }
    else if (hue < 240)
    {
      satRed = 0;
      satGreen = (double)(240 - hue) / 60.0;
      satBlue = (double)(hue - 120) / 60.0;
    }
    else
    {
      satRed = (double)(hue - 240) / 60.0;
      satGreen = 0;
      satBlue = (double)(360 - hue) / 60.0;
    }

    double tmpRed = 2*saturation*(satRed > 1 ? 1 : satRed) + 1 - saturation;
    double tmpGreen = 2*saturation*(satGreen > 1 ? 1 : satGreen) + 1 - saturation;
    double tmpBlue = 2*saturation*(satBlue > 1 ? 1 : satBlue) + 1 - saturation;

    if (lightness < 0.5)
    {
      red = (unsigned char)cdr_round(255.0*lightness*tmpRed);
      green = (unsigned char)cdr_round(255.0*lightness*tmpGreen);
      blue = (unsigned char)cdr_round(255.0*lightness*tmpBlue);
    }
    else
    {
      red = (unsigned char)cdr_round(255*((1 - lightness) * tmpRed + 2 * lightness - 1));
      green = (unsigned char)cdr_round(255*((1 - lightness) * tmpGreen + 2 * lightness - 1));
      blue = (unsigned char)cdr_round(255*((1 - lightness) * tmpBlue + 2 * lightness - 1));
    }
  }
  else if (color.m_colorModel == 0x09) // Grayscale
  {
    red = col0;
    green = col0;
    blue = col0;
  }
  else if (color.m_colorModel == 0x0c) // Lab
  {
    cmsCIELab Lab;
    Lab.L = (double)col0*100.0/255.0;
    Lab.a = (double)(signed char)col1;
    Lab.b = (double)(signed char)col2;
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformLab2RGB, &Lab, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x12) // Lab
  {
    cmsCIELab Lab;
    Lab.L = (double)col0*100.0/255.0;
    Lab.a = (double)((signed char)(col1 - 0x80));
    Lab.b = (double)((signed char)(col2 - 0x80));
    unsigned char rgb[3] = { 0, 0, 0 };
    cmsDoTransform(m_colorTransformLab2RGB, &Lab, rgb, 1);
    red = rgb[0];
    green = rgb[1];
    blue = rgb[2];
  }
  else if (color.m_colorModel == 0x19) // HKS
  {
    unsigned char HKS_red [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned char HKS_green [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned char HKS_blue [] =
    {
      0xff, 0xe3, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00,
      0xff, 0x9b, 0x1d, 0x00, 0xe2, 0x1f, 0x33, 0x00,
      0x78, 0x89, 0x3a, 0x00, 0xca, 0x22, 0x6f, 0x00,
      0xb2, 0x34, 0x86, 0x00, 0xb0, 0x3b, 0x8e, 0x00,
      0x54, 0x3c, 0xcb, 0x00, 0x28, 0x53, 0xd2, 0x00,
      0x55, 0x96, 0xd3, 0x00, 0x00, 0xd2, 0xa0, 0x00,
      0x00, 0x98, 0x55, 0x00, 0x00, 0x6a, 0x7d, 0x00,
      0x2a, 0x6a, 0x40, 0x00, 0x46, 0xc6, 0x0d, 0x00,
      0xea, 0xa9, 0x00, 0x00, 0x92, 0x6d, 0x2b, 0x00,
      0x7a, 0x5e, 0x1f, 0x00, 0x66, 0x22, 0x8d, 0x00,
      0xad, 0x80, 0x59, 0x00, 0x83, 0x41
    };

    unsigned short hks = (unsigned short)(color.m_colorValue & 0xffff)+85;
    unsigned hksIndex = hks % 86;
    hks /= 86;
    unsigned blackPercent = hks/10;
    switch (blackPercent)
    {
    case 2:
      blackPercent = 10;
      break;
    case 3:
      blackPercent = 30;
      break;
    case 4:
      blackPercent = 50;
      break;
    default:
      blackPercent = 0;
      break;
    }
    unsigned colorPercent = (hks % 10) ? (hks % 10) * 10 : 100;
    // try to avoid overflow due to rounding issues, so use first wider integer types and later, fill the unsigned char properly
    unsigned tmpRed = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_red[hksIndex]*(double)colorPercent/100.0));
    unsigned tmpGreen = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_green[hksIndex]*(double)colorPercent/100.0));
    unsigned tmpBlue = cdr_round((double)(1.0 - (double)blackPercent/100.0)*(255.0*(1.0 - (double)colorPercent/100.0) + HKS_blue[hksIndex]*(double)colorPercent/100.0));
    red = (tmpRed < 255 ? (unsigned char)tmpRed : 255);
    green = (tmpGreen < 255 ? (unsigned char)tmpGreen : 255);
    blue = (tmpBlue < 255 ? (unsigned char)tmpBlue : 255);

  }
  return (unsigned)((red << 16) | (green << 8) | blue);
}

WPXString libcdr::CDRCollector::_getRGBColorString(const libcdr::CDRColor &color)
{
  WPXString tempString;
  tempString.sprintf("#%.6x", _getRGBColor(color));
  return tempString;
}

void libcdr::CDRCollector::_fillProperties(WPXPropertyList &propList, WPXPropertyListVector &vec)
{
  if (m_fillOpacity < 1.0)
    propList.insert("draw:opacity", m_fillOpacity, WPX_PERCENT);
  if (m_currentFildId == 0)
    propList.insert("draw:fill", "none");
  else
  {
    std::map<unsigned, CDRFillStyle>::iterator iter = m_fillStyles.find(m_currentFildId);
    if (iter == m_fillStyles.end())
      propList.insert("draw:fill", "none");
    else
    {
      switch (iter->second.fillType)
      {
      case 1: // Solid
        propList.insert("draw:fill", "solid");
        propList.insert("draw:fill-color", _getRGBColorString(iter->second.color1));
        propList.insert("svg:fill-rule", "evenodd");
        break;
      case 2: // Gradient
        if (iter->second.gradient.m_stops.empty())
          propList.insert("draw:fill", "none");
        else if (iter->second.gradient.m_stops.size() == 1)
        {
          propList.insert("draw:fill", "solid");
          propList.insert("draw:fill-color", _getRGBColorString(iter->second.gradient.m_stops[0].m_color));
          propList.insert("svg:fill-rule", "evenodd");
        }
        else if (iter->second.gradient.m_stops.size() == 2)
        {
          double angle = iter->second.gradient.m_angle;
          while (angle < 0.0)
            angle += 360.0;
          while (angle > 360.0)
            angle -= 360.0;
          propList.insert("draw:fill", "gradient");
          propList.insert("draw:start-color", _getRGBColorString(iter->second.gradient.m_stops[0].m_color));
          propList.insert("draw:end-color", _getRGBColorString(iter->second.gradient.m_stops[1].m_color));
          propList.insert("draw:angle", (int)angle);
          switch (iter->second.gradient.m_type)
          {
          case 1: // linear
          case 3: // conical
            propList.insert("draw:style", "linear");
            angle += 90.0;
            while (angle < 0.0)
              angle += 360.0;
            while (angle > 360.0)
              angle -= 360.0;
            propList.insert("draw:angle", (int)angle);
            propList.insert("draw:border", (double)(iter->second.gradient.m_edgeOffset)/100.0, WPX_PERCENT);
            break;
          case 2: // radial
            propList.insert("draw:border", (2.0 * (double)(iter->second.gradient.m_edgeOffset)/100.0), WPX_PERCENT);
            propList.insert("draw:style", "radial");
            propList.insert("svg:cx", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            propList.insert("svg:cy", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            break;
          case 4: // square
            propList.insert("draw:border", (2.0 * (double)(iter->second.gradient.m_edgeOffset)/100.0), WPX_PERCENT);
            propList.insert("draw:style", "square");
            propList.insert("svg:cx", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            propList.insert("svg:cy", (double)(0.5 + iter->second.gradient.m_centerXOffset/200.0), WPX_PERCENT);
            break;
          default:
            propList.insert("draw:style", "linear");
            angle += 90.0;
            while (angle < 0.0)
              angle += 360.0;
            while (angle > 360.0)
              angle -= 360.0;
            propList.insert("draw:angle", (int)angle);
            for (unsigned i = 0; i < iter->second.gradient.m_stops.size(); i++)
            {
              libcdr::CDRGradientStop &gradStop = iter->second.gradient.m_stops[i];
              WPXPropertyList stopElement;
              stopElement.insert("svg:offset", gradStop.m_offset, WPX_PERCENT);
              stopElement.insert("svg:stop-color", _getRGBColorString(gradStop.m_color));
              stopElement.insert("svg:stop-opacity", m_fillOpacity, WPX_PERCENT);
              vec.append(stopElement);
            }
            break;
          }
        }
        else // output svg gradient as a hail mary pass towards ODG that does not really support it
        {
          propList.insert("draw:fill", "gradient");
          propList.insert("draw:style", "linear");
          double angle = iter->second.gradient.m_angle;
          angle += 90.0;
          while (angle < 0.0)
            angle += 360.0;
          while (angle > 360.0)
            angle -= 360.0;
          propList.insert("draw:angle", (int)angle);
          for (unsigned i = 0; i < iter->second.gradient.m_stops.size(); i++)
          {
            libcdr::CDRGradientStop &gradStop = iter->second.gradient.m_stops[i];
            WPXPropertyList stopElement;
            stopElement.insert("svg:offset", gradStop.m_offset, WPX_PERCENT);
            stopElement.insert("svg:stop-color", _getRGBColorString(gradStop.m_color));
            stopElement.insert("svg:stop-opacity", m_fillOpacity, WPX_PERCENT);
            vec.append(stopElement);
          }
        }
        break;
      case 7: // Pattern
      {
        std::map<unsigned, CDRPattern>::iterator iterPattern = m_patterns.find(iter->second.imageFill.id);
        if (iterPattern != m_patterns.end())
        {
          propList.insert("draw:fill", "bitmap");
          WPXBinaryData image;
          _generateBitmapFromPattern(image, iterPattern->second, iter->second.color1, iter->second.color2);
#if DUMP_PATTERN
          WPXString filename;
          filename.sprintf("pattern%.8x.bmp", iter->second.imageFill.id);
          FILE *f = fopen(filename.cstr(), "wb");
          if (f)
          {
            const unsigned char *tmpBuffer = image.getDataBuffer();
{'fraction_non_alphanumeric': 0.14310457212030916, 'fraction_numerical': 0.05672781450786852, 'mean_word_length': 3.1666795995964927, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 136, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5867300', 'n_tokens_mistral': 22240, 'n_tokens_neox': 20317, 'n_words': 4090}