Blob Blame History Raw
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libfreehand project.
 *
 * 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 <algorithm>
#include <cassert>
#include <string.h>
#include <librevenge/librevenge.h>
#include "FHCollector.h"
#include "FHConstants.h"
#include "libfreehand_utils.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#ifndef DUMP_CONTENTS
#define DUMP_CONTENTS 0
#endif
#ifndef DUMP_BINARY_OBJECTS
#define DUMP_BINARY_OBJECTS 0
#endif
#ifndef DEBUG_BOUNDING_BOX
#define DEBUG_BOUNDING_BOX 0
#endif
#ifndef DUMP_TILE_FILLS
#define DUMP_TILE_FILLS 0
#endif
#ifndef DUMP_CLIP_GROUPS
#define DUMP_CLIP_GROUPS 0
#endif

#define FH_UNINITIALIZED(pI) \
  FH_ALMOST_ZERO(pI.m_minX) && FH_ALMOST_ZERO(pI.m_minY) && FH_ALMOST_ZERO(pI.m_maxY) && FH_ALMOST_ZERO(pI.m_maxX)

namespace
{
bool isTiff(const unsigned char *buffer, unsigned long size)
{
  if (size < 4)
    return false;
  if (buffer[0] == 0x49 && buffer[1] == 0x49 && buffer[2] == 0x2a && buffer[3] == 0x00)
    return true;
  if (buffer[0] == 0x4d && buffer[1] == 0x4d && buffer[2] == 0x00 && buffer[3] == 0x2a)
    return true;
  return false;
}

bool isBmp(const unsigned char *buffer, unsigned long size)
{
  if (size < 6)
    return false;
  if (buffer[0] != 0x42)
    return false;
  if (buffer[1] != 0x4d)
    return false;
  unsigned bufferSize = ((unsigned long)buffer[2] + ((unsigned long)buffer[3] << 8) + ((unsigned long)buffer[4] << 8) + ((unsigned long)buffer[5] << 8));
  if (bufferSize != size)
    return false;
  return true;
}

bool isJpeg(const unsigned char *buffer, unsigned long size)
{
  if (size < 4)
    return false;
  if (buffer[0] != 0xff)
    return false;
  if (buffer[1] != 0xd8)
    return false;
  if (buffer[size-2] != 0xff)
    return false;
  if (buffer[size-1] != 0xd9)
    return false;
  return true;
}

bool isPng(const unsigned char *buffer, unsigned long size)
{
  if (size < 8)
    return false;
  if (buffer[0] != 0x89)
    return false;
  if (buffer[1] != 0x50)
    return false;
  if (buffer[2] != 0x4e)
    return false;
  if (buffer[3] != 0x47)
    return false;
  if (buffer[4] != 0x0d)
    return false;
  if (buffer[5] != 0x0a)
    return false;
  if (buffer[6] != 0x1a)
    return false;
  if (buffer[7] != 0x0a)
    return false;
  return true;
}

librevenge::RVNGString _getColorString(const libfreehand::FHRGBColor &color)
{
  librevenge::RVNGString colorString;
  colorString.sprintf("#%.2x%.2x%.2x", color.m_red >> 8, color.m_green >> 8, color.m_blue >> 8);
  return colorString;
}

static void _composePath(librevenge::RVNGPropertyListVector &path, bool isClosed)
{
  bool firstPoint = true;
  bool wasMove = false;
  double initialX = 0.0;
  double initialY = 0.0;
  double previousX = 0.0;
  double previousY = 0.0;
  double x = 0.0;
  double y = 0.0;
  std::vector<librevenge::RVNGPropertyList> tmpPath;

  librevenge::RVNGPropertyListVector::Iter i(path);
  for (i.rewind(); i.next();)
  {
    if (!i()["librevenge:path-action"])
      continue;
    if (i()["svg:x"] && i()["svg:y"])
    {
      bool ignoreM = false;
      x = i()["svg:x"]->getDouble();
      y = i()["svg:y"]->getDouble();
      if (firstPoint)
      {
        initialX = x;
        initialY = y;
        firstPoint = false;
        wasMove = true;
      }
      else if (i()["librevenge:path-action"]->getStr() == "M")
      {
        if (FH_ALMOST_ZERO(previousX - x) && FH_ALMOST_ZERO(previousY - y))
          ignoreM = true;
        else
        {
          if (!tmpPath.empty())
          {
            if (!wasMove)
            {
              if ((FH_ALMOST_ZERO(initialX - previousX) && FH_ALMOST_ZERO(initialY - previousY)) || isClosed)
              {
                librevenge::RVNGPropertyList node;
                node.insert("librevenge:path-action", "Z");
                tmpPath.push_back(node);
              }
            }
            else
              tmpPath.pop_back();
          }
        }

        if (!ignoreM)
        {
          initialX = x;
          initialY = y;
          wasMove = true;
        }

      }
      else
        wasMove = false;

      if (!ignoreM)
      {
        tmpPath.push_back(i());
        previousX = x;
        previousY = y;
      }

    }
    else if (i()["librevenge:path-action"]->getStr() == "Z")
    {
      if (tmpPath.back()["librevenge:path-action"] && tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
        tmpPath.push_back(i());
    }
  }
  if (!tmpPath.empty())
  {
    if (!wasMove)
    {
      if ((FH_ALMOST_ZERO(initialX - previousX) && FH_ALMOST_ZERO(initialY - previousY)) || isClosed)
      {
        if (tmpPath.back()["librevenge:path-action"] && tmpPath.back()["librevenge:path-action"]->getStr() != "Z")
        {
          librevenge::RVNGPropertyList closedPath;
          closedPath.insert("librevenge:path-action", "Z");
          tmpPath.push_back(closedPath);
        }
      }
    }
    else
      tmpPath.pop_back();
  }
  if (!tmpPath.empty())
  {
    path.clear();
    for (std::vector<librevenge::RVNGPropertyList>::const_iterator iter = tmpPath.begin(); iter != tmpPath.end(); ++iter)
      path.append(*iter);
  }
}

class ObjectRecursionGuard
{
public:
  ObjectRecursionGuard(std::deque<unsigned> &objectStack, const unsigned id)
    : m_objectStack(objectStack)
    , m_id(id)
  {
    m_objectStack.push_front(m_id);
  }

  ~ObjectRecursionGuard()
  {
    assert(!m_objectStack.empty());
    assert(m_objectStack.front() == m_id);
    m_objectStack.pop_front();
  }

private:
  std::deque<unsigned> &m_objectStack;
  const unsigned m_id;
};

}

libfreehand::FHCollector::FHCollector() :
  m_pageInfo(), m_fhTail(), m_block(), m_transforms(), m_paths(), m_strings(), m_names(), m_lists(),
  m_layers(), m_groups(), m_clipGroups(), m_currentTransforms(), m_fakeTransforms(), m_compositePaths(),
  m_pathTexts(), m_tStrings(), m_fonts(), m_tEffects(), m_paragraphs(), m_tabs(), m_textBloks(), m_textObjects(), m_charProperties(),
  m_paragraphProperties(), m_rgbColors(), m_basicFills(), m_propertyLists(),
  m_basicLines(), m_customProcs(), m_patternLines(), m_displayTexts(), m_graphicStyles(),
  m_attributeHolders(), m_data(), m_dataLists(), m_images(), m_multiColorLists(), m_linearFills(),
  m_tints(), m_lensFills(), m_radialFills(), m_newBlends(), m_filterAttributeHolders(), m_opacityFilters(),
  m_shadowFilters(), m_glowFilters(), m_tileFills(), m_symbolClasses(), m_symbolInstances(), m_patternFills(),
  m_linePatterns(), m_arrowPaths(),
  m_strokeId(0), m_fillId(0), m_contentId(0), m_textBoxNumberId(0), m_visitedObjects()
{
}

libfreehand::FHCollector::~FHCollector()
{
}

void libfreehand::FHCollector::collectPageInfo(const FHPageInfo &pageInfo)
{
  m_pageInfo = pageInfo;
}

void libfreehand::FHCollector::collectString(unsigned recordId, const librevenge::RVNGString &str)
{
  m_strings[recordId] = str;
}

void libfreehand::FHCollector::collectName(unsigned recordId, const librevenge::RVNGString &name)
{
  m_names[name] = recordId;
  if (name == "stroke")
    m_strokeId = recordId;
  if (name == "fill")
    m_fillId = recordId;
  if (name == "contents")
    m_contentId = recordId;
}

void libfreehand::FHCollector::collectPath(unsigned recordId, const libfreehand::FHPath &path)
{
  m_paths[recordId] = path;
}

void libfreehand::FHCollector::collectXform(unsigned recordId,
                                            double m11, double m21, double m12, double m22, double m13, double m23)
{
  m_transforms[recordId] = FHTransform(m11, m21, m12, m22, m13, m23);
}

void libfreehand::FHCollector::collectFHTail(unsigned /* recordId */, const FHTail &fhTail)
{
  m_fhTail = fhTail;
}

void libfreehand::FHCollector::collectBlock(unsigned recordId, const libfreehand::FHBlock &block)
{
  if (m_block.first && m_block.first != recordId)
  {
    FH_DEBUG_MSG(("FHCollector::collectBlock -- WARNING: Several \"Block\" records in the file\n"));
  }
  m_block = std::make_pair(recordId, block);
}

void libfreehand::FHCollector::collectList(unsigned recordId, const libfreehand::FHList &lst)
{
  m_lists[recordId] = lst;
}

void libfreehand::FHCollector::collectLayer(unsigned recordId, const libfreehand::FHLayer &layer)
{
  m_layers[recordId] = layer;
}

void libfreehand::FHCollector::collectGroup(unsigned recordId, const libfreehand::FHGroup &group)
{
  m_groups[recordId] = group;
}

void libfreehand::FHCollector::collectClipGroup(unsigned recordId, const libfreehand::FHGroup &group)
{
  m_clipGroups[recordId] = group;
}

void libfreehand::FHCollector::collectCompositePath(unsigned recordId, const libfreehand::FHCompositePath &compositePath)
{
  m_compositePaths[recordId] = compositePath;
}

void libfreehand::FHCollector::collectPathText(unsigned recordId, const libfreehand::FHPathText &pathText)
{
  m_pathTexts[recordId] = pathText;
}

void libfreehand::FHCollector::collectTString(unsigned recordId, const std::vector<unsigned> &elements)
{
  m_tStrings[recordId] = elements;
}

void libfreehand::FHCollector::collectAGDFont(unsigned recordId, const FHAGDFont &font)
{
  m_fonts[recordId] = font;
}

void libfreehand::FHCollector::collectTEffect(unsigned recordId, const FHTEffect &tEffect)
{
  m_tEffects[recordId] = tEffect;
}

void libfreehand::FHCollector::collectParagraph(unsigned recordId, const FHParagraph &paragraph)
{
  m_paragraphs[recordId] = paragraph;
}

void libfreehand::FHCollector::collectTabTable(unsigned recordId, const std::vector<FHTab> &tabs)
{
  if (tabs.empty()) return;
  m_tabs[recordId] = tabs;
}

void libfreehand::FHCollector::collectTextBlok(unsigned recordId, const std::vector<unsigned short> &characters)
{
  m_textBloks[recordId]  = characters;
}

void libfreehand::FHCollector::collectTextObject(unsigned recordId, const FHTextObject &textObject)
{
  m_textObjects[recordId] = textObject;
}

void libfreehand::FHCollector::collectCharProps(unsigned recordId, const FHCharProperties &charProps)
{
  m_charProperties[recordId] = charProps;
}

void libfreehand::FHCollector::collectParagraphProps(unsigned recordId, const FHParagraphProperties &paragraphProps)
{
  m_paragraphProperties[recordId] = paragraphProps;
}

void libfreehand::FHCollector::collectColor(unsigned recordId, const FHRGBColor &color)
{
  m_rgbColors[recordId] = color;
}

void libfreehand::FHCollector::collectTintColor(unsigned recordId, const FHTintColor &color)
{
  m_tints[recordId] = color;
}

void libfreehand::FHCollector::collectBasicFill(unsigned recordId, const FHBasicFill &fill)
{
  m_basicFills[recordId] = fill;
}

void libfreehand::FHCollector::collectBasicLine(unsigned recordId, const FHBasicLine &line)
{
  m_basicLines[recordId] = line;
}

void libfreehand::FHCollector::collectCustomProc(unsigned recordId, const FHCustomProc &line)
{
  m_customProcs[recordId] = line;
}

void libfreehand::FHCollector::collectPatternLine(unsigned recordId, const FHPatternLine &line)
{
  m_patternLines[recordId] = line;
}

void libfreehand::FHCollector::collectTileFill(unsigned recordId, const FHTileFill &fill)
{
  m_tileFills[recordId] = fill;
}

void libfreehand::FHCollector::collectPatternFill(unsigned recordId, const FHPatternFill &fill)
{
  m_patternFills[recordId] = fill;
}

void libfreehand::FHCollector::collectLinePattern(unsigned recordId, const FHLinePattern &line)
{
  m_linePatterns[recordId] = line;
}

void libfreehand::FHCollector::collectArrowPath(unsigned recordId, const FHPath &path)
{
  // osnola: useme
  m_arrowPaths[recordId] = path;
}

void libfreehand::FHCollector::collectPropList(unsigned recordId, const FHPropList &propertyList)
{
  m_propertyLists[recordId] = propertyList;
}

void libfreehand::FHCollector::collectDisplayText(unsigned recordId, const FHDisplayText &displayText)
{
  m_displayTexts[recordId] = displayText;
}

void libfreehand::FHCollector::collectGraphicStyle(unsigned recordId, const FHGraphicStyle &graphicStyle)
{
  m_graphicStyles[recordId] = graphicStyle;
}

void libfreehand::FHCollector::collectAttributeHolder(unsigned recordId, const FHAttributeHolder &attributeHolder)
{
  m_attributeHolders[recordId] = attributeHolder;
}

void libfreehand::FHCollector::collectFilterAttributeHolder(unsigned recordId, const FHFilterAttributeHolder &filterAttributeHolder)
{
  m_filterAttributeHolders[recordId] = filterAttributeHolder;
}

void libfreehand::FHCollector::collectData(unsigned recordId, const librevenge::RVNGBinaryData &data)
{
  m_data[recordId] = data;
}

void libfreehand::FHCollector::collectDataList(unsigned recordId, const FHDataList &list)
{
  m_dataLists[recordId] = list;
}

void libfreehand::FHCollector::collectImage(unsigned recordId, const FHImageImport &image)
{
  m_images[recordId] = image;
}

void libfreehand::FHCollector::collectMultiColorList(unsigned recordId, const std::vector<FHColorStop> &colorStops)
{
  m_multiColorLists[recordId] = colorStops;
}

void libfreehand::FHCollector::collectLinearFill(unsigned recordId, const FHLinearFill &fill)
{
  m_linearFills[recordId] = fill;
}

void libfreehand::FHCollector::collectLensFill(unsigned recordId, const FHLensFill &fill)
{
  m_lensFills[recordId] = fill;
}

void libfreehand::FHCollector::collectRadialFill(unsigned recordId, const FHRadialFill &fill)
{
  m_radialFills[recordId] = fill;
}

void libfreehand::FHCollector::collectNewBlend(unsigned recordId, const FHNewBlend &newBlend)
{
  m_newBlends[recordId] = newBlend;
}

void libfreehand::FHCollector::collectOpacityFilter(unsigned recordId, double opacity)
{
  m_opacityFilters[recordId] = opacity;
}

void libfreehand::FHCollector::collectFWShadowFilter(unsigned recordId, const FWShadowFilter &filter)
{
  m_shadowFilters[recordId] = filter;
}

void libfreehand::FHCollector::collectFWGlowFilter(unsigned recordId, const FWGlowFilter &filter)
{
  m_glowFilters[recordId] = filter;
}

void libfreehand::FHCollector::collectSymbolClass(unsigned recordId, const FHSymbolClass &symbolClass)
{
  m_symbolClasses[recordId] = symbolClass;
}

void libfreehand::FHCollector::collectSymbolInstance(unsigned recordId, const FHSymbolInstance &symbolInstance)
{
  m_symbolInstances[recordId] = symbolInstance;
}

void libfreehand::FHCollector::_normalizePath(libfreehand::FHPath &path)
{
  FHTransform trafo(1.0, 0.0, 0.0, -1.0, - m_pageInfo.m_minX, m_pageInfo.m_maxY);
  path.transform(trafo);
}

void libfreehand::FHCollector::_normalizePoint(double &x, double &y)
{
  FHTransform trafo(1.0, 0.0, 0.0, -1.0, - m_pageInfo.m_minX, m_pageInfo.m_maxY);
  trafo.applyToPoint(x, y);
}

void libfreehand::FHCollector::_getBBofPath(const FHPath *path, libfreehand::FHBoundingBox &bBox)
{
  if (!path || path->empty())
    return;

  FHPath fhPath(*path);
  unsigned short xform = fhPath.getXFormId();

  if (xform)
  {
    const FHTransform *trafo = _findTransform(xform);
    if (trafo)
      fhPath.transform(*trafo);
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    fhPath.transform(groupTransforms.top());
    groupTransforms.pop();
  }
  _normalizePath(fhPath);
  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    fhPath.transform(*iter);
  }

  FHBoundingBox tmpBBox;
  fhPath.getBoundingBox(tmpBBox.m_xmin, tmpBBox.m_ymin, tmpBBox.m_xmax, tmpBBox.m_ymax);
  bBox.merge(tmpBBox);
}

void libfreehand::FHCollector::_getBBofGroup(const FHGroup *group, libfreehand::FHBoundingBox &bBox)
{
  if (!group)
    return;

  if (group->m_xFormId)
  {
    const FHTransform *trafo = _findTransform(group->m_xFormId);
    if (trafo)
      m_currentTransforms.push(*trafo);
    else
      m_currentTransforms.push(libfreehand::FHTransform());
  }
  else
    m_currentTransforms.push(libfreehand::FHTransform());

  const std::vector<unsigned> *elements = _findListElements(group->m_elementsId);
  if (!elements)
  {
    FH_DEBUG_MSG(("ERROR: The pointed element list does not exist\n"));
    return;
  }

  for (unsigned int element : *elements)
  {
    FHBoundingBox tmpBBox;
    _getBBofSomething(element, tmpBBox);
    bBox.merge(tmpBBox);
  }

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_getBBofClipGroup(const FHGroup *group, libfreehand::FHBoundingBox &bBox)
{
  if (!group)
    return;

  if (group->m_xFormId)
  {
    const FHTransform *trafo = _findTransform(group->m_xFormId);
    if (trafo)
      m_currentTransforms.push(*trafo);
    else
      m_currentTransforms.push(libfreehand::FHTransform());
  }
  else
    m_currentTransforms.push(libfreehand::FHTransform());

  const std::vector<unsigned> *elements = _findListElements(group->m_elementsId);
  if (!elements)
  {
    FH_DEBUG_MSG(("ERROR: The pointed element list does not exist\n"));
    return;
  }

  std::vector<unsigned>::const_iterator iterVec = elements->begin();
  FHBoundingBox tmpBBox;
  _getBBofSomething(*iterVec, tmpBBox);
  bBox.merge(tmpBBox);

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_getBBofCompositePath(const FHCompositePath *compositePath, libfreehand::FHBoundingBox &bBox)
{
  if (!compositePath)
    return;

  const std::vector<unsigned> *elements = _findListElements(compositePath->m_elementsId);
  if (elements && !elements->empty())
  {
    libfreehand::FHPath fhPath;
    std::vector<unsigned>::const_iterator iter = elements->begin();
    const libfreehand::FHPath *path = _findPath(*(iter++));
    if (path)
    {
      fhPath = *path;
      if (!fhPath.getGraphicStyleId())
        fhPath.setGraphicStyleId(compositePath->m_graphicStyleId);
    }

    for (; iter != elements->end(); ++iter)
    {
      path = _findPath(*iter);
      if (path)
      {
        fhPath.appendPath(*path);
        if (!fhPath.getGraphicStyleId())
          fhPath.setGraphicStyleId(compositePath->m_graphicStyleId);
      }
    }
    FHBoundingBox tmpBBox;
    _getBBofPath(&fhPath, tmpBBox);
    bBox.merge(tmpBBox);
  }
}

void libfreehand::FHCollector::_getBBofPathText(const FHPathText *pathText, libfreehand::FHBoundingBox &bBox)
{
  if (!pathText)
    return;

  _getBBofDisplayText(_findDisplayText(pathText->m_displayTextId),bBox);
}

void libfreehand::FHCollector::_getBBofTextObject(const FHTextObject *textObject, libfreehand::FHBoundingBox &bBox)
{
  if (!textObject)
    return;

  double xa = textObject->m_startX;
  double ya = textObject->m_startY;
  double xb = textObject->m_startX + textObject->m_width;
  double yb = textObject->m_startY + textObject->m_height;
  double xc = xa;
  double yc = yb;
  double xd = xb;
  double yd = ya;
  unsigned xFormId = textObject->m_xFormId;
  if (xFormId)
  {
    const FHTransform *trafo = _findTransform(xFormId);
    if (trafo)
    {
      trafo->applyToPoint(xa, ya);
      trafo->applyToPoint(xb, yb);
      trafo->applyToPoint(xc, yc);
      trafo->applyToPoint(xd, yd);
    }
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    groupTransforms.top().applyToPoint(xa, ya);
    groupTransforms.top().applyToPoint(xb, yb);
    groupTransforms.top().applyToPoint(xc, yc);
    groupTransforms.top().applyToPoint(xd, yd);
    groupTransforms.pop();
  }
  _normalizePoint(xa, ya);
  _normalizePoint(xb, yb);
  _normalizePoint(xc, yc);
  _normalizePoint(xd, yd);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    iter->applyToPoint(xa, ya);
    iter->applyToPoint(xb, yb);
    iter->applyToPoint(xc, yc);
    iter->applyToPoint(xd, yd);
  }

  FHBoundingBox tmpBBox;
  if (xa < tmpBBox.m_xmin) tmpBBox.m_xmin = xa;
  if (xb < tmpBBox.m_xmin) tmpBBox.m_xmin = xb;
  if (xc < tmpBBox.m_xmin) tmpBBox.m_xmin = xc;
  if (xd < tmpBBox.m_xmin) tmpBBox.m_xmin = xd;

  if (xa > tmpBBox.m_xmax) tmpBBox.m_xmax = xa;
  if (xb > tmpBBox.m_xmax) tmpBBox.m_xmax = xb;
  if (xc > tmpBBox.m_xmax) tmpBBox.m_xmax = xc;
  if (xd > tmpBBox.m_xmax) tmpBBox.m_xmax = xd;

  if (ya < tmpBBox.m_ymin) tmpBBox.m_ymin = ya;
  if (yb < tmpBBox.m_ymin) tmpBBox.m_ymin = yb;
  if (yc < tmpBBox.m_ymin) tmpBBox.m_ymin = yc;
  if (yd < tmpBBox.m_ymin) tmpBBox.m_ymin = yd;

  if (ya > tmpBBox.m_ymax) tmpBBox.m_ymax = ya;
  if (yb > tmpBBox.m_ymax) tmpBBox.m_ymax = yb;
  if (yc > tmpBBox.m_ymax) tmpBBox.m_ymax = yc;
  if (yd > tmpBBox.m_ymax) tmpBBox.m_ymax = yd;
  bBox.merge(tmpBBox);
}

void libfreehand::FHCollector::_getBBofDisplayText(const FHDisplayText *displayText, libfreehand::FHBoundingBox &bBox)
{
  if (!displayText)
    return;

  double xa = displayText->m_startX;
  double ya = displayText->m_startY;
  double xb = displayText->m_startX + displayText->m_width;
  double yb = displayText->m_startY + displayText->m_height;
  double xc = xa;
  double yc = yb;
  double xd = xb;
  double yd = ya;
  unsigned xFormId = displayText->m_xFormId;
  if (xFormId)
  {
    const FHTransform *trafo = _findTransform(xFormId);
    if (trafo)
    {
      trafo->applyToPoint(xa, ya);
      trafo->applyToPoint(xb, yb);
      trafo->applyToPoint(xc, yc);
      trafo->applyToPoint(xd, yd);
    }
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    groupTransforms.top().applyToPoint(xa, ya);
    groupTransforms.top().applyToPoint(xb, yb);
    groupTransforms.top().applyToPoint(xc, yc);
    groupTransforms.top().applyToPoint(xd, yd);
    groupTransforms.pop();
  }
  _normalizePoint(xa, ya);
  _normalizePoint(xb, yb);
  _normalizePoint(xc, yc);
  _normalizePoint(xd, yd);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    iter->applyToPoint(xa, ya);
    iter->applyToPoint(xb, yb);
    iter->applyToPoint(xc, yc);
    iter->applyToPoint(xd, yd);
  }

  FHBoundingBox tmpBBox;
  if (xa < tmpBBox.m_xmin) tmpBBox.m_xmin = xa;
  if (xb < tmpBBox.m_xmin) tmpBBox.m_xmin = xb;
  if (xc < tmpBBox.m_xmin) tmpBBox.m_xmin = xc;
  if (xd < tmpBBox.m_xmin) tmpBBox.m_xmin = xd;

  if (xa > tmpBBox.m_xmax) tmpBBox.m_xmax = xa;
  if (xb > tmpBBox.m_xmax) tmpBBox.m_xmax = xb;
  if (xc > tmpBBox.m_xmax) tmpBBox.m_xmax = xc;
  if (xd > tmpBBox.m_xmax) tmpBBox.m_xmax = xd;

  if (ya < tmpBBox.m_ymin) tmpBBox.m_ymin = ya;
  if (yb < tmpBBox.m_ymin) tmpBBox.m_ymin = yb;
  if (yc < tmpBBox.m_ymin) tmpBBox.m_ymin = yc;
  if (yd < tmpBBox.m_ymin) tmpBBox.m_ymin = yd;

  if (ya > tmpBBox.m_ymax) tmpBBox.m_ymax = ya;
  if (yb > tmpBBox.m_ymax) tmpBBox.m_ymax = yb;
  if (yc > tmpBBox.m_ymax) tmpBBox.m_ymax = yc;
  if (yd > tmpBBox.m_ymax) tmpBBox.m_ymax = yd;
  bBox.merge(tmpBBox);
}

void libfreehand::FHCollector::_getBBofImageImport(const FHImageImport *image, libfreehand::FHBoundingBox &bBox)
{
  if (!image)
    return;

  double xa = image->m_startX;
  double ya = image->m_startY;
  double xb = image->m_startX + image->m_width;
  double yb = image->m_startY + image->m_height;
  double xc = xa;
  double yc = yb;
  double xd = xb;
  double yd = ya;
  unsigned xFormId = image->m_xFormId;
  if (xFormId)
  {
    const FHTransform *trafo = _findTransform(xFormId);
    if (trafo)
    {
      trafo->applyToPoint(xa, ya);
      trafo->applyToPoint(xb, yb);
      trafo->applyToPoint(xc, yc);
      trafo->applyToPoint(xd, yd);
    }
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    groupTransforms.top().applyToPoint(xa, ya);
    groupTransforms.top().applyToPoint(xb, yb);
    groupTransforms.top().applyToPoint(xc, yc);
    groupTransforms.top().applyToPoint(xd, yd);
    groupTransforms.pop();
  }
  _normalizePoint(xa, ya);
  _normalizePoint(xb, yb);
  _normalizePoint(xc, yc);
  _normalizePoint(xd, yd);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    iter->applyToPoint(xa, ya);
    iter->applyToPoint(xb, yb);
    iter->applyToPoint(xc, yc);
    iter->applyToPoint(xd, yd);
  }

  FHBoundingBox tmpBBox;
  if (xa < tmpBBox.m_xmin) tmpBBox.m_xmin = xa;
  if (xb < tmpBBox.m_xmin) tmpBBox.m_xmin = xb;
  if (xc < tmpBBox.m_xmin) tmpBBox.m_xmin = xc;
  if (xd < tmpBBox.m_xmin) tmpBBox.m_xmin = xd;

  if (xa > tmpBBox.m_xmax) tmpBBox.m_xmax = xa;
  if (xb > tmpBBox.m_xmax) tmpBBox.m_xmax = xb;
  if (xc > tmpBBox.m_xmax) tmpBBox.m_xmax = xc;
  if (xd > tmpBBox.m_xmax) tmpBBox.m_xmax = xd;

  if (ya < tmpBBox.m_ymin) tmpBBox.m_ymin = ya;
  if (yb < tmpBBox.m_ymin) tmpBBox.m_ymin = yb;
  if (yc < tmpBBox.m_ymin) tmpBBox.m_ymin = yc;
  if (yd < tmpBBox.m_ymin) tmpBBox.m_ymin = yd;

  if (ya > tmpBBox.m_ymax) tmpBBox.m_ymax = ya;
  if (yb > tmpBBox.m_ymax) tmpBBox.m_ymax = yb;
  if (yc > tmpBBox.m_ymax) tmpBBox.m_ymax = yc;
  if (yd > tmpBBox.m_ymax) tmpBBox.m_ymax = yd;
  bBox.merge(tmpBBox);
}

void libfreehand::FHCollector::_getBBofNewBlend(const FHNewBlend * /* newBlend */, libfreehand::FHBoundingBox & /* bBox */)
{
}

void libfreehand::FHCollector::_getBBofSymbolInstance(const FHSymbolInstance *symbolInstance, libfreehand::FHBoundingBox &bBox)
{
  if (!symbolInstance)
    return;

  m_currentTransforms.push(symbolInstance->m_xForm);

  const FHSymbolClass *symbolClass = _findSymbolClass(symbolInstance->m_symbolClassId);
  if (symbolClass)
  {
    FHBoundingBox tmpBBox;
    _getBBofSomething(symbolClass->m_groupId, tmpBBox);
    bBox.merge(tmpBBox);
  }

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_getBBofSomething(unsigned somethingId, libfreehand::FHBoundingBox &bBox)
{
  if (!somethingId)
    return;

  FHBoundingBox tmpBBox;
  _getBBofGroup(_findGroup(somethingId), tmpBBox);
  _getBBofClipGroup(_findClipGroup(somethingId), tmpBBox);
  _getBBofPathText(_findPathText(somethingId), tmpBBox);
  _getBBofPath(_findPath(somethingId), tmpBBox);
  _getBBofCompositePath(_findCompositePath(somethingId), tmpBBox);
  _getBBofTextObject(_findTextObject(somethingId), tmpBBox);
  _getBBofDisplayText(_findDisplayText(somethingId), tmpBBox);
  _getBBofImageImport(_findImageImport(somethingId), tmpBBox);
  _getBBofNewBlend(_findNewBlend(somethingId), tmpBBox);
  _getBBofSymbolInstance(_findSymbolInstance(somethingId), tmpBBox);
  bBox.merge(tmpBBox);
}


void libfreehand::FHCollector::_outputPath(const libfreehand::FHPath *path, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !path || path->empty())
    return;

  FHPath fhPath(*path);
  librevenge::RVNGPropertyList propList;
  _appendStrokeProperties(propList, fhPath.getGraphicStyleId());
  _appendFillProperties(propList, fhPath.getGraphicStyleId());
  unsigned contentId = _findContentId(fhPath.getGraphicStyleId());
  if (fhPath.getEvenOdd())
    propList.insert("svg:fill-rule", "evenodd");

  unsigned short xform = fhPath.getXFormId();

  if (xform)
  {
    const FHTransform *trafo = _findTransform(xform);
    if (trafo)
      fhPath.transform(*trafo);
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    fhPath.transform(groupTransforms.top());
    groupTransforms.pop();
  }
  _normalizePath(fhPath);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    fhPath.transform(*iter);
  }

  librevenge::RVNGPropertyListVector propVec;
  fhPath.writeOut(propVec);
  if (propList["draw:fill"] && propList["draw:fill"]->getStr() != "none")
    _composePath(propVec, true);
  else
    _composePath(propVec, fhPath.isClosed());
  librevenge::RVNGPropertyList pList;
  pList.insert("svg:d", propVec);
  if (contentId)
    painter->openGroup(librevenge::RVNGPropertyList());
  painter->setStyle(propList);
  painter->drawPath(pList);
  if (contentId)
  {
    FHBoundingBox bBox;
    fhPath.getBoundingBox(bBox.m_xmin, bBox.m_ymin, bBox.m_xmax, bBox.m_ymax);
    FHTransform trafo(1.0, 0.0, 0.0, 1.0, - bBox.m_xmin, - bBox.m_ymin);
    m_fakeTransforms.push_back(trafo);
    librevenge::RVNGStringVector svgOutput;
    librevenge::RVNGSVGDrawingGenerator generator(svgOutput, "");
    propList.clear();
    propList.insert("svg:width", bBox.m_xmax - bBox.m_xmin);
    propList.insert("svg:height", bBox.m_ymax - bBox.m_ymin);
    generator.startPage(propList);
    _outputSomething(contentId, &generator);
    generator.endPage();
    if (!svgOutput.empty() && svgOutput[0].size() > 140) // basically empty svg if it is not fullfilled
    {
      const char *header =
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
      librevenge::RVNGBinaryData output((const unsigned char *)header, strlen(header));
      output.append((unsigned char *)svgOutput[0].cstr(), strlen(svgOutput[0].cstr()));
#if DUMP_CONTENTS
      {
        librevenge::RVNGString filename;
        filename.sprintf("fhcontents%.4x.svg", contentId);
        FILE *f = fopen(filename.cstr(), "wb");
        if (f)
        {
          const unsigned char *tmpBuffer = output.getDataBuffer();
          for (unsigned long k = 0; k < output.size(); k++)
            fprintf(f, "%c",tmpBuffer[k]);
          fclose(f);
        }
      }
#endif
      propList.clear();
      propList.insert("draw:stroke", "none");
      propList.insert("draw:fill", "bitmap");
      propList.insert("librevenge:mime-type", "image/svg+xml");
      propList.insert("style:repeat", "stretch");
      propList.insert("draw:fill-image", output);
      painter->setStyle(propList);
      painter->drawPath(pList);
    }
    if (!m_fakeTransforms.empty())
      m_fakeTransforms.pop_back();
    painter->closeGroup();
  }
#if DEBUG_BOUNDING_BOX
  {
    librevenge::RVNGPropertyList rectangleProps;
    rectangleProps.insert("draw:fill", "none");
    rectangleProps.insert("draw:stroke", "solid");
    painter->setStyle(rectangleProps);
    double xmin, ymin, xmax, ymax;
    fhPath.getBoundingBox(xmin, ymin, xmax, ymax);
    librevenge::RVNGPropertyList rectangleList;
    rectangleList.insert("svg:x", xmin);
    rectangleList.insert("svg:y", ymin);
    rectangleList.insert("svg:width", xmax - xmin);
    rectangleList.insert("svg:height", ymax - ymin);
    painter->drawRectangle(rectangleList);
  }
#endif
}

void libfreehand::FHCollector::_outputSomething(unsigned somethingId, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !somethingId)
    return;
  if (find(m_visitedObjects.begin(), m_visitedObjects.end(), somethingId) != m_visitedObjects.end())
    return;

  const ObjectRecursionGuard guard(m_visitedObjects, somethingId);

  _outputGroup(_findGroup(somethingId), painter);
  _outputClipGroup(_findClipGroup(somethingId), painter);
  _outputPathText(_findPathText(somethingId), painter);
  _outputPath(_findPath(somethingId), painter);
  _outputCompositePath(_findCompositePath(somethingId), painter);
  _outputTextObject(_findTextObject(somethingId), painter);
  _outputDisplayText(_findDisplayText(somethingId), painter);
  _outputImageImport(_findImageImport(somethingId), painter);
  _outputNewBlend(_findNewBlend(somethingId), painter);
  _outputSymbolInstance(_findSymbolInstance(somethingId), painter);
}

void libfreehand::FHCollector::_outputGroup(const libfreehand::FHGroup *group, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !group)
    return;

  if (group->m_xFormId)
  {
    const FHTransform *trafo = _findTransform(group->m_xFormId);
    if (trafo)
      m_currentTransforms.push(*trafo);
    else
      m_currentTransforms.push(libfreehand::FHTransform());
  }
  else
    m_currentTransforms.push(libfreehand::FHTransform());

  const std::vector<unsigned> *elements = _findListElements(group->m_elementsId);
  if (!elements)
  {
    FH_DEBUG_MSG(("ERROR: The pointed element list does not exist\n"));
    return;
  }

  if (!elements->empty())
  {
    painter->openGroup(librevenge::RVNGPropertyList());
    for (unsigned int element : *elements)
      _outputSomething(element, painter);
    painter->closeGroup();
  }

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_outputClipGroup(const libfreehand::FHGroup *group, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !group)
    return;

  const std::vector<unsigned> *elements = _findListElements(group->m_elementsId);
  if (!elements)
  {
    FH_DEBUG_MSG(("ERROR: The pointed element list does not exist\n"));
    return;
  }

  if (!elements->empty())
  {
    std::vector<unsigned>::const_iterator iter = elements->begin();
    const FHPath *path = _findPath(*iter);
    if (!path)
      _outputGroup(group, painter);
    else
    {
      if (group->m_xFormId)
      {
        const FHTransform *trafo = _findTransform(group->m_xFormId);
        if (trafo)
          m_currentTransforms.push(*trafo);
        else
          m_currentTransforms.push(libfreehand::FHTransform());
      }
      else
        m_currentTransforms.push(libfreehand::FHTransform());

      librevenge::RVNGPropertyList propList;
      FHPath fhPath(*path);
      _appendStrokeProperties(propList, fhPath.getGraphicStyleId());
      _appendFillProperties(propList, fhPath.getGraphicStyleId());
      if (fhPath.getEvenOdd())
        propList.insert("svg:fill-rule", "evenodd");
      unsigned short xform = fhPath.getXFormId();

      if (xform)
      {
        const FHTransform *trafo = _findTransform(xform);
        if (trafo)
          fhPath.transform(*trafo);
      }
      std::stack<FHTransform> groupTransforms(m_currentTransforms);
      while (!groupTransforms.empty())
      {
        fhPath.transform(groupTransforms.top());
        groupTransforms.pop();
      }
      _normalizePath(fhPath);

      for (std::vector<FHTransform>::const_iterator iterVec = m_fakeTransforms.begin(); iterVec != m_fakeTransforms.end(); ++iterVec)
      {
        fhPath.transform(*iterVec);
      }

      if (!m_currentTransforms.empty())
        m_currentTransforms.pop();

      librevenge::RVNGPropertyListVector propVec;
      fhPath.writeOut(propVec);
      _composePath(propVec, true);
      librevenge::RVNGPropertyList pList;
      pList.insert("svg:d", propVec);


      FHBoundingBox bBox;
      fhPath.getBoundingBox(bBox.m_xmin, bBox.m_ymin, bBox.m_xmax, bBox.m_ymax);
      FHTransform trafo(1.0, 0.0, 0.0, 1.0, - bBox.m_xmin, - bBox.m_ymin);
      m_fakeTransforms.push_back(trafo);
      librevenge::RVNGStringVector svgOutput;
      librevenge::RVNGSVGDrawingGenerator generator(svgOutput, "");
      propList.clear();
      propList.insert("svg:width", bBox.m_xmax - bBox.m_xmin);
      propList.insert("svg:height", bBox.m_ymax - bBox.m_ymin);
      generator.startPage(propList);
      _outputGroup(group, &generator);
      generator.endPage();
      if (!svgOutput.empty() && svgOutput[0].size() > 140) // basically empty svg if it is not fullfilled
      {
        const char *header =
          "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
        librevenge::RVNGBinaryData output((const unsigned char *)header, strlen(header));
        output.append((unsigned char *)svgOutput[0].cstr(), strlen(svgOutput[0].cstr()));
#if DUMP_CLIP_GROUPS
        {
          librevenge::RVNGString filename;
          filename.sprintf("freehandclipgroup%.4x.svg", group->m_elementsId);
          FILE *f = fopen(filename.cstr(), "wb");
          if (f)
          {
            const unsigned char *tmpBuffer = output.getDataBuffer();
            for (unsigned long k = 0; k < output.size(); k++)
              fprintf(f, "%c",tmpBuffer[k]);
            fclose(f);
          }
        }
#endif
        propList.insert("draw:stroke", "none");
        propList.insert("draw:fill", "bitmap");
        propList.insert("librevenge:mime-type", "image/svg+xml");
        propList.insert("style:repeat", "stretch");
        propList.insert("draw:fill-image", output);
        painter->setStyle(propList);
        painter->drawPath(pList);
      }
      if (!m_fakeTransforms.empty())
        m_fakeTransforms.pop_back();
    }
  }
}

void libfreehand::FHCollector::_outputPathText(const libfreehand::FHPathText *pathText, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !pathText)
    return;

  _outputDisplayText(_findDisplayText(pathText->m_displayTextId), painter);
}

void libfreehand::FHCollector::_outputNewBlend(const libfreehand::FHNewBlend *newBlend, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !newBlend)
    return;

  m_currentTransforms.push(libfreehand::FHTransform());

  painter->openGroup(librevenge::RVNGPropertyList());
  const std::vector<unsigned> *elements1 = _findListElements(newBlend->m_list1Id);
  if (elements1 && !elements1->empty())
  {
    for (unsigned int iterVec : *elements1)
      _outputSomething(iterVec, painter);
  }
  const std::vector<unsigned> *elements2 = _findListElements(newBlend->m_list2Id);
  if (elements2 && !elements2->empty())
  {
    for (unsigned int iterVec : *elements2)
      _outputSomething(iterVec, painter);
  }
  const std::vector<unsigned> *elements3 = _findListElements(newBlend->m_list3Id);
  if (elements3 && !elements3->empty())
  {
    for (unsigned int iterVec : *elements3)
      _outputSomething(iterVec, painter);
  }
  painter->closeGroup();

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_outputSymbolInstance(const libfreehand::FHSymbolInstance *symbolInstance, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !symbolInstance)
    return;

  m_currentTransforms.push(symbolInstance->m_xForm);

  const FHSymbolClass *symbolClass = _findSymbolClass(symbolInstance->m_symbolClassId);
  if (symbolClass)
  {
    _outputSomething(symbolClass->m_groupId, painter);
  }

  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::outputDrawing(librevenge::RVNGDrawingInterface *painter)
{

#if DUMP_BINARY_OBJECTS
  for (std::map<unsigned, FHImageImport>::const_iterator iterImage = m_images.begin(); iterImage != m_images.end(); ++iterImage)
  {
    librevenge::RVNGBinaryData data = getImageData(iterImage->second.m_dataListId);
    librevenge::RVNGString filename;
    filename.sprintf("freehanddump%.4x.%s", iterImage->first, iterImage->second.m_format.empty() ? "bin" : iterImage->second.m_format.cstr());
    FILE *f = fopen(filename.cstr(), "wb");
    if (f)
    {
      const unsigned char *tmpBuffer = data.getDataBuffer();
      for (unsigned long k = 0; k < data.size(); k++)
        fprintf(f, "%c",tmpBuffer[k]);
      fclose(f);
    }
  }
#endif

  if (!painter)
    return;

  if (!m_fhTail.m_blockId || m_fhTail.m_blockId != m_block.first)
  {
    FH_DEBUG_MSG(("WARNING: FHTail points to an invalid Block ID\n"));
    m_fhTail.m_blockId = m_block.first;
  }
  if (!m_fhTail.m_blockId)
  {
    FH_DEBUG_MSG(("ERROR: Block record is absent from this file\n"));
    return;
  }

  if (FH_UNINITIALIZED(m_pageInfo))
    m_pageInfo = m_fhTail.m_pageInfo;

  painter->startDocument(librevenge::RVNGPropertyList());
  librevenge::RVNGPropertyList propList;
  propList.insert("svg:height", m_pageInfo.m_maxY - m_pageInfo.m_minY);
  propList.insert("svg:width", m_pageInfo.m_maxX - m_pageInfo.m_minX);
  painter->startPage(propList);

  unsigned layerListId = m_block.second.m_layerListId;

  const std::vector<unsigned> *elements = _findListElements(layerListId);
  if (elements)
  {
    for (unsigned int element : *elements)
    {
      _outputLayer(element, painter);
    }
  }
  painter->endPage();
  painter->endDocument();
}

void libfreehand::FHCollector::_outputLayer(unsigned layerId, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter)
    return;

  std::map<unsigned, FHLayer>::const_iterator layerIter = m_layers.find(layerId);
  if (layerIter == m_layers.end())
  {
    FH_DEBUG_MSG(("ERROR: Could not find the referenced layer\n"));
    return;
  }

  if (layerIter->second.m_visibility != 3)
    return;

  unsigned layerElementsListId = layerIter->second.m_elementsId;
  if (!layerElementsListId)
  {
    FH_DEBUG_MSG(("ERROR: Layer points to invalid element list\n"));
    return;
  }

  const std::vector<unsigned> *elements = _findListElements(layerElementsListId);
  if (!elements)
  {
    FH_DEBUG_MSG(("ERROR: The pointed element list does not exist\n"));
    return;
  }

  for (unsigned int element : *elements)
    _outputSomething(element, painter);
}

void libfreehand::FHCollector::_outputCompositePath(const libfreehand::FHCompositePath *compositePath, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !compositePath)
    return;

  const std::vector<unsigned> *elements = _findListElements(compositePath->m_elementsId);
  if (elements && !elements->empty())
  {
    libfreehand::FHPath fhPath;
    std::vector<unsigned>::const_iterator iter = elements->begin();
    const libfreehand::FHPath *path = _findPath(*(iter++));
    if (path)
    {
      fhPath = *path;
      if (!fhPath.getGraphicStyleId())
        fhPath.setGraphicStyleId(compositePath->m_graphicStyleId);
    }

    for (; iter != elements->end(); ++iter)
    {
      path = _findPath(*iter);
      if (path)
      {
        fhPath.appendPath(*path);
        if (!fhPath.getGraphicStyleId())
          fhPath.setGraphicStyleId(compositePath->m_graphicStyleId);
      }
    }
    _outputPath(&fhPath, painter);
  }
}

void libfreehand::FHCollector::_outputTextObject(const libfreehand::FHTextObject *textObject, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !textObject)
    return;

  double width=textObject->m_width;
  double height=textObject->m_height;
  unsigned num[]= {textObject->m_colNum,textObject->m_rowNum};
  double decalX[]= {width+textObject->m_colSep,0};
  double decalY[]= {0, height+textObject->m_rowSep};
  if (textObject->m_rowBreakFirst)
  {
    std::swap(num[0],num[1]);
    std::swap(decalX[0],decalX[1]);
    std::swap(decalY[0],decalY[1]);
  }
  for (unsigned int &i : num)
  {
    if (i<=0 || i>10)
    {
      FH_DEBUG_MSG(("libfreehand::FHCollector::_outputTextObject: the number of row/col seems bad\n"));
      i=1;
    }
  }
  ++m_textBoxNumberId;
  for (unsigned dim0=0; dim0<num[0]; ++dim0)
  {
    for (unsigned dim1=0; dim1<num[1]; ++dim1)
    {
      unsigned id = dim0*num[1]+dim1;
      double rotation = 0, finalHeight = 0, finalWidth = 0, xmid=0, ymid=0;
      bool useShapeBox=false;
      if ((width<=0 || height<=0) && textObject->m_pathId)
      {
        /* the position are not set for TFOnPath, so we must look for the shape box

           Note: the width and height seem better, the x,y position are still quite random :-~
        */
        FHBoundingBox bbox;
        _getBBofSomething(textObject->m_pathId, bbox);
        useShapeBox=true;
        xmid=0.5*(bbox.m_xmin+bbox.m_xmax);
        ymid=0.5*(bbox.m_ymin+bbox.m_ymax);
        width=finalWidth=(bbox.m_xmax-bbox.m_xmin);
        height=finalHeight=(bbox.m_ymax-bbox.m_ymin);
      }
      if (!useShapeBox)
      {
#ifdef HAVE_CHAINED_TEXTBOX
        // useme when we can chain frames in Draw
        double startX=textObject->m_startX+dim0*decalX[0]+dim1*decalX[1];
        double startY=textObject->m_startY+dim0*decalY[0]+dim1*decalY[1];
#else
        /* if the number of row/column is greater than 1, we have a
           big problem. Let increase the text-box size to contain all
           the chained text-boxes...
         */
        double startX=textObject->m_startX;
        double startY=textObject->m_startY;
        width += (num[0]-1)*decalX[0]+(num[1]-1)*decalX[1];
        height += (num[0]-1)*decalY[0]+(num[1]-1)*decalY[1];
#endif
        double xa = startX;
        double ya = startY;
        double xb = startX + width;
        double yb = startY + height;
        double xc = xa;
        double yc = yb;
        unsigned xFormId = textObject->m_xFormId;
        if (xFormId)
        {
          const FHTransform *trafo = _findTransform(xFormId);
          if (trafo)
          {
            trafo->applyToPoint(xa, ya);
            trafo->applyToPoint(xb, yb);
            trafo->applyToPoint(xc, yc);
          }
        }
        std::stack<FHTransform> groupTransforms(m_currentTransforms);
        while (!groupTransforms.empty())
        {
          groupTransforms.top().applyToPoint(xa, ya);
          groupTransforms.top().applyToPoint(xb, yb);
          groupTransforms.top().applyToPoint(xc, yc);
          groupTransforms.pop();
        }
        _normalizePoint(xa, ya);
        _normalizePoint(xb, yb);
        _normalizePoint(xc, yc);

        for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
        {
          iter->applyToPoint(xa, ya);
          iter->applyToPoint(xb, yb);
          iter->applyToPoint(xc, yc);
        }

        rotation = atan2(yb-yc, xb-xc);
        finalHeight = sqrt((xc-xa)*(xc-xa) + (yc-ya)*(yc-ya));
        finalWidth = sqrt((xc-xb)*(xc-xb) + (yc-yb)*(yc-yb));
        xmid = (xa + xb) / 2.0;
        ymid = (ya + yb) / 2.0;
      }

      librevenge::RVNGPropertyList textObjectProps;
      textObjectProps.insert("svg:x", xmid - width / 2.0);
      textObjectProps.insert("svg:y", ymid + height / 2.0);
      textObjectProps.insert("svg:height", finalHeight);
      textObjectProps.insert("svg:width", finalWidth);
      if (!FH_ALMOST_ZERO(rotation))
      {
        textObjectProps.insert("librevenge:rotate", rotation * 180.0 / M_PI);
        textObjectProps.insert("librevenge:rotate-cx",xmid);
        textObjectProps.insert("librevenge:rotate-cy",ymid);
      }
#ifdef HAVE_CHAINED_TEXTBOX
      if (id)
      {
        librevenge::RVNGString name;
        name.sprintf("Textbox%d-%d",m_textBoxNumberId,id);
        textObjectProps.insert("librevenge:frame-name",name);
      }
      if (id+1!=num[0]*num[1])
      {
        librevenge::RVNGString name;
        name.sprintf("Textbox%d-%d",m_textBoxNumberId,id+1);
        textObjectProps.insert("librevenge:next-frame-name",name);
      }
#endif
      painter->startTextObject(textObjectProps);

      if (id==0)
      {
        const std::vector<unsigned> *elements = _findTStringElements(textObject->m_tStringId);
        unsigned actPos=0;
        if (elements && !elements->empty())
        {
          for (unsigned int element : *elements)
            _outputParagraph(_findParagraph(element), painter, actPos, textObject->m_beginPos, textObject->m_endPos);
        }
      }
      painter->endTextObject();

#if !defined(HAVE_CHAINED_TEXTBOX)
      break;
#endif
    }
#if !defined(HAVE_CHAINED_TEXTBOX)
    break;
#endif
  }
}

void libfreehand::FHCollector::_outputParagraph(const libfreehand::FHParagraph *paragraph, librevenge::RVNGDrawingInterface *painter, unsigned &actPos, unsigned minPos, unsigned maxPos)
{
  if (!painter || !paragraph)
    return;
  bool paragraphOpened=false;
  std::map<unsigned, std::vector<unsigned short> >::const_iterator iter = m_textBloks.find(paragraph->m_textBlokId);
  if (iter != m_textBloks.end())
  {

    for (std::vector<std::pair<unsigned, unsigned> >::size_type i = 0; i < paragraph->m_charStyleIds.size(); ++i)
    {
      if (actPos>=maxPos) break;
      unsigned lastChar=i+1 < paragraph->m_charStyleIds.size() ? paragraph->m_charStyleIds[i+1].first : iter->second.size();
      unsigned numChar=lastChar-paragraph->m_charStyleIds[i].first;
      unsigned nextPos=actPos+numChar;
      if (nextPos<minPos)
      {
        actPos=nextPos;
        continue;
      }
      if (!paragraphOpened)
      {
        librevenge::RVNGPropertyList propList;
        _appendParagraphProperties(propList, paragraph->m_paraStyleId);
        painter->openParagraph(propList);
        paragraphOpened=true;
      }
      unsigned fChar=paragraph->m_charStyleIds[i].first + (actPos<minPos ? minPos-actPos : 0);
      numChar=lastChar-fChar;
      if (actPos+numChar>maxPos) numChar=maxPos-actPos;
      _outputTextRun(&(iter->second), fChar, numChar, paragraph->m_charStyleIds[i].second, painter);
      actPos=nextPos;
    }
  }
  ++actPos; // EOL
  if (paragraphOpened)
    painter->closeParagraph();
}

void libfreehand::FHCollector::_appendCharacterProperties(librevenge::RVNGPropertyList &propList, unsigned charPropsId)
{
  std::map<unsigned, FHCharProperties>::const_iterator iter = m_charProperties.find(charPropsId);
  if (iter == m_charProperties.end())
    return;
  const FHCharProperties &charProps = iter->second;
  if (charProps.m_fontNameId)
  {
    std::map<unsigned, librevenge::RVNGString>::const_iterator iterString = m_strings.find(charProps.m_fontNameId);
    if (iterString != m_strings.end())
      propList.insert("fo:font-name", iterString->second);
  }
  propList.insert("fo:font-size", charProps.m_fontSize, librevenge::RVNG_POINT);
  if (charProps.m_fontId)
    _appendFontProperties(propList, charProps.m_fontId);
  if (charProps.m_textColorId)
  {
    std::map<unsigned, FHBasicFill>::const_iterator iterBasicFill = m_basicFills.find(charProps.m_textColorId);
    if (iterBasicFill != m_basicFills.end() && iterBasicFill->second.m_colorId)
    {
      librevenge::RVNGString color = getColorString(iterBasicFill->second.m_colorId);
      if (!color.empty())
        propList.insert("fo:color", color);
    }
  }
  FHTEffect const *eff=_findTEffect(charProps.m_tEffectId);
  if (eff && eff->m_nameId)
  {
    std::map<unsigned, librevenge::RVNGString>::const_iterator iterString = m_strings.find(eff->m_nameId);
    if (iterString != m_strings.end())
    {
      librevenge::RVNGString const &type=iterString->second;
      if (type=="InlineEffect")   // inside col1, outside col0
      {
        propList.insert("fo:font-weight", "bold");
        librevenge::RVNGString color = getColorString(eff->m_colorId[1]);
        if (!color.empty())
          propList.insert("fo:color", color);
      }
      else if (type=="ShadowEffect")
        propList.insert("fo:text-shadow", "1pt 1pt");
      else if (type=="ZoomEffect")
      {
        propList.insert("style:font-relief", "embossed");
        propList.insert("fo:text-shadow", "1pt -1pt");
        librevenge::RVNGString color = getColorString(eff->m_colorId[0]);
        if (!color.empty())
          propList.insert("fo:color", color);
      }
      else
      {
        FH_DEBUG_MSG(("libfreehand::FHCollector::_appendCharacterProperties: find unknown effect %s\n",type.cstr()));
      }
    }
  }
  for (std::map<unsigned,double>::const_iterator it=charProps.m_idToDoubleMap.begin(); it!=charProps.m_idToDoubleMap.end(); ++it)
  {
    switch (it->first)
    {
    case FH_BASELN_SHIFT:
    {
      if (it->second<=0 && it->second>=0) break;
      librevenge::RVNGString value;
      double fontSize=(charProps.m_fontSize>0) ? charProps.m_fontSize : 24.;
      value.sprintf("%g%%",100.*it->second/fontSize);
      propList.insert("style:text-position", value);
      break;
    }
    case FH_HOR_SCALE:
      if (it->second<=1 && it->second>=1) break;
      propList.insert("style:text-scale", it->second, librevenge::RVNG_PERCENT);
      break;
    case FH_RNG_KERN:
      if (it->second<=0 && it->second>=0) break;
      propList.insert("fo:letter-spacing", it->second*charProps.m_fontSize, librevenge::RVNG_POINT);
      break;
    default:
      break;
    }
  }
}

void libfreehand::FHCollector::_appendCharacterProperties(librevenge::RVNGPropertyList &propList, const FH3CharProperties &charProps)
{
  if (charProps.m_fontNameId)
  {
    std::map<unsigned, librevenge::RVNGString>::const_iterator iterString = m_strings.find(charProps.m_fontNameId);
    if (iterString != m_strings.end())
      propList.insert("fo:font-name", iterString->second);
  }
  propList.insert("fo:font-size", charProps.m_fontSize, librevenge::RVNG_POINT);
  if (charProps.m_fontColorId)
  {
    librevenge::RVNGString color = getColorString(charProps.m_fontColorId);
    if (!color.empty())
      propList.insert("fo:color", color);
  }
  if (charProps.m_fontStyle & 1)
    propList.insert("fo:font-weight", "bold");
  if (charProps.m_fontStyle & 2)
    propList.insert("fo:font-style", "italic");
  if (charProps.m_letterSpacing<0 || charProps.m_letterSpacing>0)
    propList.insert("fo:letter-spacing", charProps.m_letterSpacing, librevenge::RVNG_POINT);
  if (charProps.m_horizontalScale<1 || charProps.m_horizontalScale>1)
    propList.insert("style:text-scale", charProps.m_horizontalScale, librevenge::RVNG_PERCENT);
  if (charProps.m_baselineShift<0 || charProps.m_baselineShift>0)
  {
    librevenge::RVNGString value;
    double fontSize=(charProps.m_fontSize>0) ? charProps.m_fontSize : 24.;
    value.sprintf("%g%%",100.*charProps.m_baselineShift/fontSize);
    propList.insert("style:text-position", value);
  }
  FHTEffect const *eff=_findTEffect(charProps.m_textEffsId);
  if (eff && eff->m_shortNameId)
  {
    std::map<unsigned, librevenge::RVNGString>::const_iterator iterString = m_strings.find(eff->m_shortNameId);
    if (iterString != m_strings.end())
    {
      librevenge::RVNGString const &type=iterString->second;
      if (type=="inlin")   // inside col1, outside col0
        propList.insert("fo:font-weight", "bold");
      else if (type=="otw stol")
        propList.insert("style:text-outline", "true");
      else if (type=="stob")
        propList.insert("fo:font-style", "italic");
      else if (type=="stsh")
        propList.insert("fo:text-shadow", "1pt 1pt");
      else if (type=="sthv")
        propList.insert("fo:font-weight", "bold");
      else if (type=="extrude")
      {
        propList.insert("style:font-relief", "embossed");
        propList.insert("fo:text-shadow", "1pt -1pt");
        librevenge::RVNGString color = getColorString(eff->m_colorId[0]);
        if (!color.empty())
          propList.insert("fo:color", color);
      }
      else
      {
        FH_DEBUG_MSG(("libfreehand::FHCollector::_appendCharacterProperties: find unknown effect %s\n",type.cstr()));
      }
    }
  }
}

void libfreehand::FHCollector::_appendTabProperties(librevenge::RVNGPropertyList &propList, const libfreehand::FHTab &tab)
{
  switch (tab.m_type)
  {
  case 0:
  case 4: // unsure, look like a left tab
  default:
    break;
  case 1:
    propList.insert("style:type", "right");
    break;
  case 2:
    propList.insert("style:type", "center");
    break;
  case 3:
    propList.insert("style:type", "char");
    propList.insert("style:char", "."); // checkme
    break;
  }
  propList.insert("style:position", tab.m_position, librevenge::RVNG_POINT);
}

void libfreehand::FHCollector::_appendParagraphProperties(librevenge::RVNGPropertyList & /* propList */, const FH3ParaProperties & /* paraProps */)
{
}

void libfreehand::FHCollector::_appendParagraphProperties(librevenge::RVNGPropertyList &propList, unsigned paragraphPropsId)
{
  std::map<unsigned, FHParagraphProperties>::const_iterator iter = m_paragraphProperties.find(paragraphPropsId);
  if (iter == m_paragraphProperties.end())
    return;
  FHParagraphProperties const &para=iter->second;
  for (const auto &it : para.m_idToZoneIdMap)
  {
    switch (it.first)
    {
    case FH_PARA_TAB_TABLE_ID:
      if (m_tabs.find(it.second)!=m_tabs.end())
      {
        std::vector<FHTab> const &tabs=m_tabs.find(it.second)->second;
        if (tabs.empty())
          break;
        librevenge::RVNGPropertyListVector tabVect;
        for (auto tab : tabs)
        {
          librevenge::RVNGPropertyList tabList;
          _appendTabProperties(tabList, tab);
          tabVect.append(tabList);
        }
        propList.insert("style:tab-stops", tabVect);
      }
      break;
    default:
      break;
    }
  }
  for (std::map<unsigned,double>::const_iterator it=para.m_idToDoubleMap.begin(); it!=para.m_idToDoubleMap.end(); ++it)
  {
    switch (it->first)
    {
    case FH_PARA_LEFT_INDENT:
      propList.insert("fo:margin-left", it->second, librevenge::RVNG_POINT);
      break;
    case FH_PARA_RIGHT_INDENT:
      propList.insert("fo:margin-right", it->second, librevenge::RVNG_POINT);
      break;
    case FH_PARA_TEXT_INDENT:
      propList.insert("fo:text-indent", it->second, librevenge::RVNG_POINT);
      break;
    case FH_PARA_SPC_ABOVE:
      propList.insert("fo:margin-top", it->second, librevenge::RVNG_POINT);
      break;
    case FH_PARA_SPC_BELLOW:
      propList.insert("fo:margin-bottom", it->second, librevenge::RVNG_POINT);
      break;
    case FH_PARA_LEADING:
      if (it->second<=0 && it->second>=0) break;
      if (para.m_idToIntMap.find(FH_PARA_LEADING_TYPE)==para.m_idToIntMap.end())
      {
        FH_DEBUG_MSG(("libfreehand::FHCollector::_appendParagraphProperties: can not find the leading type\n"));
        break;
      }
      switch (para.m_idToIntMap.find(FH_PARA_LEADING_TYPE)->second)
      {
      case 0: // delta in point
        propList.insert("fo:line-height",1.+it->second/(it->second>0 ? 12 : 24), librevenge::RVNG_PERCENT);
        break;
      case 1:
        propList.insert("fo:line-height",it->second, librevenge::RVNG_POINT);
        break;
      case 2:
        propList.insert("fo:line-height",it->second, librevenge::RVNG_PERCENT);
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
  }
  for (const auto &it : para.m_idToIntMap)
  {
    switch (it.first)
    {
    case FH_PARA_TEXT_ALIGN:
      switch (it.second)
      {
      case 0:
        propList.insert("fo:text-align", "left");
        break;
      case 1:
        propList.insert("fo:text-align", "end");
        break;
      case 2:
        propList.insert("fo:text-align", "center");
        break;
      case 3:
        propList.insert("fo:text-align", "justify");
        break;
      default:
        break;
      }
      break;
    case FH_PARA_KEEP_SAME_LINE:
      if (it.second==1)
        propList.insert("fo:keep-together", "always");
      break;
    case FH_PARA_LEADING_TYPE: // done with FH_PARA_LEADING
    default:
      break;
    }
  }
}

void libfreehand::FHCollector::_outputDisplayText(const libfreehand::FHDisplayText *displayText, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !displayText)
    return;

  double xa = displayText->m_startX;
  double ya = displayText->m_startY;
  double xb = displayText->m_startX + displayText->m_width;
  double yb = displayText->m_startY + displayText->m_height;
  double xc = xa;
  double yc = yb;
  unsigned xFormId = displayText->m_xFormId;
  if (xFormId)
  {
    const FHTransform *trafo = _findTransform(xFormId);
    if (trafo)
    {
      trafo->applyToPoint(xa, ya);
      trafo->applyToPoint(xb, yb);
      trafo->applyToPoint(xc, yc);
    }
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    groupTransforms.top().applyToPoint(xa, ya);
    groupTransforms.top().applyToPoint(xb, yb);
    groupTransforms.top().applyToPoint(xc, yc);
    groupTransforms.pop();
  }
  _normalizePoint(xa, ya);
  _normalizePoint(xb, yb);
  _normalizePoint(xc, yc);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    iter->applyToPoint(xa, ya);
    iter->applyToPoint(xb, yb);
    iter->applyToPoint(xc, yc);
  }

  double rotation = atan2(yb-yc, xb-xc);
  double height = sqrt((xc-xa)*(xc-xa) + (yc-ya)*(yc-ya));
  double width = sqrt((xc-xb)*(xc-xb) + (yc-yb)*(yc-yb));
  double xmid = (xa + xb) / 2.0;
  double ymid = (ya + yb) / 2.0;

  librevenge::RVNGPropertyList textObjectProps;
  textObjectProps.insert("svg:x", xmid - displayText->m_width / 2.0);
  textObjectProps.insert("svg:y", ymid + displayText->m_height / 2.0);
  textObjectProps.insert("svg:height", height);
  textObjectProps.insert("svg:width", width);
  for (int i=0; i<4; ++i) // osnola: let assume that there is no padding
  {
    char const *(padding[])= {"fo:padding-left","fo:padding-right","fo:padding-top","fo:padding-bottom"};
    textObjectProps.insert(padding[i],0,librevenge::RVNG_POINT);
  }
  if (!FH_ALMOST_ZERO(rotation))
  {
    textObjectProps.insert("librevenge:rotate", rotation * 180.0 / M_PI);
    textObjectProps.insert("librevenge:rotate-cx",xmid);
    textObjectProps.insert("librevenge:rotate-cy",ymid);
  }
  if (displayText->m_justify==4)  // top-down: checkme do nothing
    textObjectProps.insert("style:writing-mode", "tb-lr");
  painter->startTextObject(textObjectProps);

  std::vector<FH3ParaProperties>::const_iterator iterPara = displayText->m_paraProps.begin();
  std::vector<FH3CharProperties>::const_iterator iterChar = displayText->m_charProps.begin();

  FH3ParaProperties paraProps = *iterPara++;
  FH3CharProperties charProps = *iterChar++;
  librevenge::RVNGString text;
  std::vector<unsigned char>::size_type i = 0;

  librevenge::RVNGPropertyList paraPropList;
  _appendParagraphProperties(paraPropList, paraProps);
  switch (displayText->m_justify)
  {
  case 0: // left
    break;
  case 1:
    paraPropList.insert("fo:text-align", "center");
    break;
  case 2:
    paraPropList.insert("fo:text-align", "end");
    break;
  case 3:
    paraPropList.insert("fo:text-align", "justify");
    break;
  case 4:
  default:
    break;
  }
  if (charProps.m_leading>0)
    paraPropList.insert("fo:line-height",charProps.m_leading,librevenge::RVNG_POINT);
  else
    paraPropList.insert("fo:line-height",1.,librevenge::RVNG_PERCENT);
  painter->openParagraph(paraPropList);
  bool isParagraphOpened = true;

  librevenge::RVNGPropertyList charPropList;
  _appendCharacterProperties(charPropList, charProps);
  painter->openSpan(charPropList);
  bool isSpanOpened = true;

  while (i < displayText->m_characters.size())
  {
    _appendMacRoman(text, displayText->m_characters[i++]);
    if (i > paraProps.m_offset)
    {
      if (!text.empty())
        painter->insertText(text);
      text.clear();
      if (isParagraphOpened)
      {
        if (isSpanOpened)
        {
          painter->closeSpan();
          isSpanOpened = false;
        }
        painter->closeParagraph();
        isParagraphOpened = false;
      }
      if (iterPara != displayText->m_paraProps.end())
      {
        paraProps = *iterPara++;
      }
    }
    if (i > charProps.m_offset)
    {
      if (!text.empty())
        painter->insertText(text);
      text.clear();
      if (isSpanOpened)
      {
        painter->closeSpan();
        isSpanOpened = false;
      }
      if (iterChar != displayText->m_charProps.end())
      {
        charProps = *iterChar++;
      }
    }
    if (i >= displayText->m_characters.size())
      break;
    if (!isParagraphOpened)
    {
#if 0
      paraPropList.clear();
      _appendParagraphProperties(paraPropList, paraProps);
#endif
      if (charProps.m_leading>0)
        paraPropList.insert("fo:line-height",charProps.m_leading,librevenge::RVNG_POINT);
      else
        paraPropList.insert("fo:line-height",1.,librevenge::RVNG_PERCENT);
      painter->openParagraph(paraPropList);
      isParagraphOpened = true;
      if (!isSpanOpened)
      {
        charPropList.clear();
        _appendCharacterProperties(charPropList, charProps);
        painter->openSpan(charPropList);
        isSpanOpened = true;
      }
    }
    if (!isSpanOpened)
    {
      charPropList.clear();
      _appendCharacterProperties(charPropList, charProps);
      painter->openSpan(charPropList);
      isSpanOpened = true;
    }
  }
  if (!text.empty())
    painter->insertText(text);
  if (isSpanOpened)
    painter->closeSpan();
  if (isParagraphOpened)
    painter->closeParagraph();

  painter->endTextObject();
}

void libfreehand::FHCollector::_outputImageImport(const FHImageImport *image, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !image)
    return;

  librevenge::RVNGPropertyList propList;
  _appendStrokeProperties(propList, image->m_graphicStyleId);
  _appendFillProperties(propList, image->m_graphicStyleId);
  double xa = image->m_startX;
  double ya = image->m_startY;
  double xb = image->m_startX + image->m_width;
  double yb = image->m_startY + image->m_height;
  double xc = xa;
  double yc = yb;
  unsigned xFormId = image->m_xFormId;
  if (xFormId)
  {
    const FHTransform *trafo = _findTransform(xFormId);
    if (trafo)
    {
      trafo->applyToPoint(xa, ya);
      trafo->applyToPoint(xb, yb);
      trafo->applyToPoint(xc, yc);
    }
  }
  std::stack<FHTransform> groupTransforms(m_currentTransforms);
  while (!groupTransforms.empty())
  {
    groupTransforms.top().applyToPoint(xa, ya);
    groupTransforms.top().applyToPoint(xb, yb);
    groupTransforms.top().applyToPoint(xc, yc);
    groupTransforms.pop();
  }
  _normalizePoint(xa, ya);
  _normalizePoint(xb, yb);
  _normalizePoint(xc, yc);

  for (std::vector<FHTransform>::const_iterator iter = m_fakeTransforms.begin(); iter != m_fakeTransforms.end(); ++iter)
  {
    iter->applyToPoint(xa, ya);
    iter->applyToPoint(xb, yb);
    iter->applyToPoint(xc, yc);
  }

  double rotation = atan2(yb-yc, xb-xc);
  double height = sqrt((xc-xa)*(xc-xa) + (yc-ya)*(yc-ya));
  double width = sqrt((xc-xb)*(xc-xb) + (yc-yb)*(yc-yb));
  double xmid = (xa + xb) / 2.0;
  double ymid = (ya + yb) / 2.0;

  librevenge::RVNGPropertyList imageProps;
  imageProps.insert("svg:x", xmid - width / 2.0);
  imageProps.insert("svg:y", ymid - height / 2.0);
  imageProps.insert("svg:height", height);
  imageProps.insert("svg:width", width);
  if (!FH_ALMOST_ZERO(rotation))
    imageProps.insert("librevenge:rotate", rotation * 180.0 / M_PI);

  imageProps.insert("librevenge:mime-type", "whatever");
  librevenge::RVNGBinaryData data = getImageData(image->m_dataListId);

  if (data.empty())
    return;

  const unsigned char *buffer = data.getDataBuffer();
  unsigned long size = data.size();
  if (isTiff(buffer, size))
    imageProps.insert("librevenge:mime-type", "image/tiff");
  else if (isBmp(buffer, size))
    imageProps.insert("librevenge:mime-type", "image/bmp");
  else if (isJpeg(buffer, size))
    imageProps.insert("librevenge:mime-type", "image/jpeg");
  else if (isPng(buffer, size))
    imageProps.insert("librevenge:mime-type", "image/png");

  imageProps.insert("office:binary-data", data);
  painter->setStyle(propList);
  painter->drawGraphicObject(imageProps);
}

void libfreehand::FHCollector::_outputTextRun(const std::vector<unsigned short> *characters, unsigned offset, unsigned length,
                                              unsigned charStyleId, librevenge::RVNGDrawingInterface *painter)
{
  if (!painter || !characters || characters->empty())
    return;
  librevenge::RVNGPropertyList propList;
  _appendCharacterProperties(propList, charStyleId);
  painter->openSpan(propList);
  std::vector<unsigned short> tmpChars;
  bool lastIsSpace=false;
  for (unsigned i = offset; i < length+offset && i < characters->size(); ++i)
  {
    unsigned c=(*characters)[i];
    if (c=='\t' || (c==' ' && lastIsSpace))
    {
      if (!tmpChars.empty())
      {
        librevenge::RVNGString text;
        _appendUTF16(text, tmpChars);
        painter->insertText(text);
        tmpChars.clear();
      }
      if (c=='\t')
        painter->insertTab();
      else
        painter->insertSpace();
      continue;
    }
    else
    {
      if (c<=0x1f)
      {
        switch (c)
        {
        case 0xb: // end of column
          break;
        case 0x1f: // optional hyphen
          break;
        default:
          FH_DEBUG_MSG(("libfreehand::FHCollector::_outputTextRun: find character %x\n", c));
          break;
        }
      }
      else
        tmpChars.push_back(c);
    }
    lastIsSpace=c==' ';
  }
  if (!tmpChars.empty())
  {
    librevenge::RVNGString text;
    _appendUTF16(text, tmpChars);
    painter->insertText(text);
  }
  painter->closeSpan();
}

const std::vector<unsigned> *libfreehand::FHCollector::_findListElements(unsigned id)
{
  std::map<unsigned, FHList>::const_iterator iter = m_lists.find(id);
  if (iter != m_lists.end())
    return &(iter->second.m_elements);
  return nullptr;
}


void libfreehand::FHCollector::_appendFontProperties(librevenge::RVNGPropertyList &propList, unsigned agdFontId)
{
  std::map<unsigned, FHAGDFont>::const_iterator iter = m_fonts.find(agdFontId);
  if (iter == m_fonts.end())
    return;
  const FHAGDFont &font = iter->second;
  if (font.m_fontNameId)
  {
    std::map<unsigned, librevenge::RVNGString>::const_iterator iterString = m_strings.find(font.m_fontNameId);
    if (iterString != m_strings.end())
      propList.insert("fo:font-name", iterString->second);
  }
  propList.insert("fo:font-size", font.m_fontSize, librevenge::RVNG_POINT);
  if (font.m_fontStyle & 1)
    propList.insert("fo:font-weight", "bold");
  if (font.m_fontStyle & 2)
    propList.insert("fo:font-style", "italic");
}

void libfreehand::FHCollector::_appendFillProperties(librevenge::RVNGPropertyList &propList, unsigned graphicStyleId)
{
  if (!propList["draw:fill"])
    propList.insert("draw:fill", "none");
  if (graphicStyleId && find(m_visitedObjects.begin(), m_visitedObjects.end(), graphicStyleId) == m_visitedObjects.end())
  {
    const ObjectRecursionGuard guard(m_visitedObjects, graphicStyleId);
    const FHPropList *propertyList = _findPropList(graphicStyleId);
    if (propertyList)
    {
      if (propertyList->m_parentId)
        _appendFillProperties(propList, propertyList->m_parentId);
      std::map<unsigned, unsigned>::const_iterator iter = propertyList->m_elements.find(m_fillId);
      if (iter != propertyList->m_elements.end())
      {
        _appendBasicFill(propList, _findBasicFill(iter->second));
        _appendLinearFill(propList, _findLinearFill(iter->second));
        _appendLensFill(propList, _findLensFill(iter->second));
        _appendRadialFill(propList, _findRadialFill(iter->second));
        _appendTileFill(propList, _findTileFill(iter->second));
        _appendPatternFill(propList, _findPatternFill(iter->second));
        _appendCustomProcFill(propList, _findCustomProc(iter->second));
      }
    }
    else
    {
      const FHGraphicStyle *graphicStyle = _findGraphicStyle(graphicStyleId);
      if (graphicStyle)
      {
        if (graphicStyle->m_parentId)
          _appendFillProperties(propList, graphicStyle->m_parentId);
        unsigned fillId = _findFillId(*graphicStyle);;
        if (fillId)
        {
          _appendBasicFill(propList, _findBasicFill(fillId));
          _appendLinearFill(propList, _findLinearFill(fillId));
          _appendLensFill(propList, _findLensFill(fillId));
          _appendRadialFill(propList, _findRadialFill(fillId));
          _appendTileFill(propList, _findTileFill(fillId));
          _appendPatternFill(propList, _findPatternFill(fillId));
          _appendCustomProcFill(propList, _findCustomProc(fillId));
        }
        else
        {
          const FHFilterAttributeHolder *filterAttributeHolder = _findFilterAttributeHolder(*graphicStyle);
          if (filterAttributeHolder)
          {
            if (filterAttributeHolder->m_graphicStyleId)
              _appendFillProperties(propList, filterAttributeHolder->m_graphicStyleId);
            if (filterAttributeHolder->m_filterId)
              _applyFilter(propList, filterAttributeHolder->m_filterId);
          }
        }
      }
    }
  }
}

void libfreehand::FHCollector::_appendStrokeProperties(librevenge::RVNGPropertyList &propList, unsigned graphicStyleId)
{
  if (!propList["draw:stroke"])
    propList.insert("draw:stroke", "none");
  if (graphicStyleId && find(m_visitedObjects.begin(), m_visitedObjects.end(), graphicStyleId) == m_visitedObjects.end())
  {
    const ObjectRecursionGuard guard(m_visitedObjects, graphicStyleId);
    const FHPropList *propertyList = _findPropList(graphicStyleId);
    if (propertyList)
    {
      if (propertyList->m_parentId)
        _appendStrokeProperties(propList, propertyList->m_parentId);
      std::map<unsigned, unsigned>::const_iterator iter = propertyList->m_elements.find(m_strokeId);
      if (iter != propertyList->m_elements.end())
      {
        _appendBasicLine(propList, _findBasicLine(iter->second));
        _appendPatternLine(propList, _findPatternLine(iter->second));
        _appendCustomProcLine(propList, _findCustomProc(iter->second));
      }
    }
    else
    {
      const FHGraphicStyle *graphicStyle = _findGraphicStyle(graphicStyleId);
      if (graphicStyle)
      {
        if (graphicStyle->m_parentId)
          _appendStrokeProperties(propList, graphicStyle->m_parentId);
        unsigned strokeId = _findStrokeId(*graphicStyle);
        if (strokeId)
        {
          _appendBasicLine(propList, _findBasicLine(strokeId));
          _appendPatternLine(propList, _findPatternLine(strokeId));
          _appendCustomProcLine(propList, _findCustomProc(strokeId));
        }
        else
        {
          const FHFilterAttributeHolder *filterAttributeHolder = _findFilterAttributeHolder(*graphicStyle);
          if (filterAttributeHolder)
          {
            if (filterAttributeHolder->m_graphicStyleId)
              _appendFillProperties(propList, filterAttributeHolder->m_graphicStyleId);
            if (filterAttributeHolder->m_filterId)
              _applyFilter(propList, filterAttributeHolder->m_filterId);
          }
        }
      }
    }
  }
}

void libfreehand::FHCollector::_appendBasicFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHBasicFill *basicFill)
{
  if (!basicFill)
    return;
  propList.insert("draw:fill", "solid");
  librevenge::RVNGString color = getColorString(basicFill->m_colorId);
  if (!color.empty())
    propList.insert("draw:fill-color", color);
  else
    propList.insert("draw:fill-color", "#000000");
}

void libfreehand::FHCollector::_appendCustomProcFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHCustomProc *fill)
{
  if (!fill || fill->m_ids.empty())
    return;
  propList.insert("draw:fill", "solid");
  librevenge::RVNGString color = getColorString(fill->m_ids[0]);
  if (!color.empty())
    propList.insert("draw:fill-color", color);
  else
    propList.insert("draw:fill-color", "#000000");
}

unsigned libfreehand::FHCollector::_findContentId(unsigned graphicStyleId)
{
  if (graphicStyleId)
  {
    const FHPropList *propertyList = _findPropList(graphicStyleId);
    if (propertyList)
    {
      std::map<unsigned, unsigned>::const_iterator iter = propertyList->m_elements.find(m_contentId);
      if (iter != propertyList->m_elements.end())
        return iter->second;
    }
    else
    {
      const FHGraphicStyle *graphicStyle = _findGraphicStyle(graphicStyleId);
      if (graphicStyle)
      {
        std::map<unsigned, unsigned>::const_iterator iter = graphicStyle->m_elements.find(m_contentId);
        if (iter != graphicStyle->m_elements.end())
          return iter->second;
      }
    }
  }
  return 0;
}

void libfreehand::FHCollector::_appendLinearFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHLinearFill *linearFill)
{
  if (!linearFill)
    return;
  propList.insert("draw:fill", "gradient");
  propList.insert("draw:style", "linear");
  double angle = 90.0 - linearFill->m_angle;
  while (angle < 0.0)
    angle += 360.0;
  while (angle > 360.0)
    angle -= 360.0;
  propList.insert("draw:angle", angle, librevenge::RVNG_GENERIC);

  const std::vector<FHColorStop> *multiColorList = _findMultiColorList(linearFill->m_multiColorListId);
  if (multiColorList && multiColorList->size() > 1)
  {
    librevenge::RVNGString color = getColorString((*multiColorList)[0].m_colorId);
    if (!color.empty())
      propList.insert("draw:start-color", color);
    color = getColorString((*multiColorList)[1].m_colorId);
    if (!color.empty())
      propList.insert("draw:end-color", color);
  }
  else
  {
    librevenge::RVNGString color = getColorString(linearFill->m_color1Id);
    if (!color.empty())
      propList.insert("draw:start-color", color);
    color = getColorString(linearFill->m_color2Id);
    if (!color.empty())
      propList.insert("draw:end-color", color);
  }
}

void libfreehand::FHCollector::_applyFilter(librevenge::RVNGPropertyList &propList, unsigned filterId)
{
  if (!filterId)
    return;
  _appendOpacity(propList, _findOpacityFilter(filterId));
  _appendShadow(propList, _findFWShadowFilter(filterId));
  _appendGlow(propList, _findFWGlowFilter(filterId));
}

void libfreehand::FHCollector::_appendOpacity(librevenge::RVNGPropertyList &propList, const double *opacity)
{
  if (!opacity)
    return;
  if (propList["draw:fill"] && propList["draw:fill"]->getStr() != "none")
    propList.insert("draw:opacity", *opacity, librevenge::RVNG_PERCENT);
  if (propList["draw:stroke"] && propList["draw:stroke"]->getStr() != "none")
    propList.insert("svg:stroke-opacity", *opacity, librevenge::RVNG_PERCENT);
}

void libfreehand::FHCollector::_appendShadow(librevenge::RVNGPropertyList &propList, const libfreehand::FWShadowFilter *filter)
{
  if (!filter)
    return;
  if (!filter->m_inner)
  {
    propList.insert("draw:shadow","visible"); // for ODG
    propList.insert("draw:shadow-offset-x",filter->m_distribution * cos(M_PI * filter->m_angle / 180.0));
    propList.insert("draw:shadow-offset-y",filter->m_distribution * sin(M_PI * filter->m_angle / 180.0));
    propList.insert("draw:shadow-color",getColorString(filter->m_colorId));
    propList.insert("draw:shadow-opacity",filter->m_opacity, librevenge::RVNG_PERCENT);
  }
}

void libfreehand::FHCollector::_appendGlow(librevenge::RVNGPropertyList & /* propList */, const libfreehand::FWGlowFilter *filter)
{
  if (!filter)
    return;
}

void libfreehand::FHCollector::_appendLensFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHLensFill *lensFill)
{
  if (!lensFill)
    return;

  if (lensFill->m_colorId)
  {
    propList.insert("draw:fill", "solid");
    librevenge::RVNGString color = getColorString(lensFill->m_colorId);
    if (!color.empty())
      propList.insert("draw:fill-color", color);
    else
      propList.insert("draw:fill", "none");
  }
  else
    propList.insert("draw:fill", "none");

  switch (lensFill->m_mode)
  {
  case FH_LENSFILL_MODE_TRANSPARENCY:
    propList.insert("draw:opacity", lensFill->m_value / 100.0, librevenge::RVNG_PERCENT);
    break;
  case FH_LENSFILL_MODE_MONOCHROME:
    propList.insert("draw:fill", "none");
    propList.insert("draw:color-mode", "greyscale");
    break;
  case FH_LENSFILL_MODE_MAGNIFY:
    propList.insert("draw:fill", "none");
    break;
  case FH_LENSFILL_MODE_LIGHTEN: // emulate by semi-transparent white fill
    propList.insert("draw:fill", "solid");
    propList.insert("draw:fill-color", "#FFFFFF");
    propList.insert("draw:opacity", lensFill->m_value / 100.0, librevenge::RVNG_PERCENT);
    break;
  case FH_LENSFILL_MODE_DARKEN: // emulate by semi-transparent black fill
    propList.insert("draw:fill", "solid");
    propList.insert("draw:fill-color", "#000000");
    propList.insert("draw:opacity", lensFill->m_value / 100.0, librevenge::RVNG_PERCENT);
    break;
  case FH_LENSFILL_MODE_INVERT:
    propList.insert("draw:fill", "none");
    break;
  default:
    break;
  }
}

void libfreehand::FHCollector::_appendRadialFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHRadialFill *radialFill)
{
  if (!radialFill)
    return;

  propList.insert("draw:fill", "gradient");
  propList.insert("draw:style", "radial");
  propList.insert("svg:cx", radialFill->m_cx, librevenge::RVNG_PERCENT);
  propList.insert("svg:cy", radialFill->m_cy, librevenge::RVNG_PERCENT);

  const std::vector<FHColorStop> *multiColorList = _findMultiColorList(radialFill->m_multiColorListId);
  if (multiColorList && multiColorList->size() > 1)
  {
    librevenge::RVNGString color = getColorString((*multiColorList)[0].m_colorId);
    if (!color.empty())
      propList.insert("draw:start-color", color);
    color = getColorString((*multiColorList)[1].m_colorId);
    if (!color.empty())
      propList.insert("draw:end-color", color);
  }
  else
  {
    librevenge::RVNGString color = getColorString(radialFill->m_color1Id);
    if (!color.empty())
      propList.insert("draw:start-color", color);
    color = getColorString(radialFill->m_color2Id);
    if (!color.empty())
      propList.insert("draw:end-color", color);
  }
}

void libfreehand::FHCollector::_appendTileFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHTileFill *tileFill)
{
  if (!tileFill || !(tileFill->m_groupId))
    return;

  const FHTransform *trafo = _findTransform(tileFill->m_xFormId);
  if (trafo)
    m_currentTransforms.push(*trafo);
  else
    m_currentTransforms.push(FHTransform());

  FHBoundingBox bBox;
  _getBBofSomething(tileFill->m_groupId, bBox);
  if (bBox.isValid() && !FH_ALMOST_ZERO(bBox.m_xmax - bBox.m_xmin) && !FH_ALMOST_ZERO(bBox.m_ymax - bBox.m_ymin))
  {
    FHTransform fakeTrafo(tileFill->m_scaleX, 0.0, 0.0, tileFill->m_scaleY, - bBox.m_xmin, -bBox.m_ymin);
    m_fakeTransforms.push_back(fakeTrafo);

    librevenge::RVNGStringVector svgOutput;
    librevenge::RVNGSVGDrawingGenerator generator(svgOutput, "");
    librevenge::RVNGPropertyList pList;
    pList.insert("svg:width", tileFill->m_scaleX * (bBox.m_xmax - bBox.m_xmin));
    pList.insert("svg:height", tileFill->m_scaleY * (bBox.m_ymax - bBox.m_ymin));
    generator.startPage(pList);

    _outputSomething(tileFill->m_groupId, &generator);
    generator.endPage();
    if (!svgOutput.empty() && svgOutput[0].size() > 140) // basically empty svg if it is not fullfilled
    {
      const char *header =
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
      librevenge::RVNGBinaryData output((const unsigned char *)header, strlen(header));
      output.append((unsigned char *)svgOutput[0].cstr(), strlen(svgOutput[0].cstr()));
#if DUMP_TILE_FILLS
      {
        librevenge::RVNGString filename;
        filename.sprintf("freehandtilefill%.4x.svg", tileFill->m_groupId);
        FILE *f = fopen(filename.cstr(), "wb");
        if (f)
        {
          const unsigned char *tmpBuffer = output.getDataBuffer();
          for (unsigned long k = 0; k < output.size(); k++)
            fprintf(f, "%c",tmpBuffer[k]);
          fclose(f);
        }
      }
#endif
      propList.insert("draw:fill", "bitmap");
      propList.insert("draw:fill-image", output);
      propList.insert("draw:fill-image-width", tileFill->m_scaleX * (bBox.m_xmax - bBox.m_xmin));
      propList.insert("draw:fill-image-height", tileFill->m_scaleY * (bBox.m_ymax - bBox.m_ymin));
      propList.insert("librevenge:mime-type", "image/svg+xml");
      propList.insert("style:repeat", "repeat");
    }

    if (!m_fakeTransforms.empty())
      m_fakeTransforms.pop_back();
  }
  if (!m_currentTransforms.empty())
    m_currentTransforms.pop();
}

void libfreehand::FHCollector::_appendPatternFill(librevenge::RVNGPropertyList &propList, const libfreehand::FHPatternFill *patternFill)
{
  if (!patternFill)
    return;
  librevenge::RVNGBinaryData output;
  _generateBitmapFromPattern(output, patternFill->m_colorId, patternFill->m_pattern);
  propList.insert("draw:fill", "bitmap");
  propList.insert("draw:fill-image", output);
  propList.insert("librevenge:mime-type", "image/bmp");
  propList.insert("style:repeat", "repeat");
}

void libfreehand::FHCollector::_appendLinePattern(librevenge::RVNGPropertyList &propList, const libfreehand::FHLinePattern *linePattern)
{
  if (!linePattern || linePattern->m_dashes.size()<=1)
    return;
  int nDots1=0, nDots2=0;
  double size1=0, size2=0, totalGap=0.0;
  for (size_t c=0; c+1 < linePattern->m_dashes.size();)
  {
    double sz=linePattern->m_dashes[c++];
    if (nDots2 && (sz<size2||sz>size2))
    {
      static bool first=true;
      if (first)
      {
        FH_DEBUG_MSG(("libfreehand::FHCollector::_appendLinePattern: can not set some dash\n"));
        first = false;
      }
      break;
    }
    if (nDots2)
      nDots2++;
    else if (!nDots1 || (sz>=size1 && sz <= size1))
    {
      nDots1++;
      size1=sz;
    }
    else
    {
      nDots2=1;
      size2=sz;
    }
    totalGap += linePattern->m_dashes[c++];
  }
  propList.insert("draw:stroke", "dash");
  propList.insert("draw:dots1", nDots1);
  propList.insert("draw:dots1-length", double(size1), librevenge::RVNG_POINT);
  if (nDots2)
  {
    propList.insert("draw:dots2", nDots2);
    propList.insert("draw:dots2-length", double(size2), librevenge::RVNG_POINT);
  }
  const double distance = ((nDots1 + nDots2) > 0) ? double(totalGap)/double(nDots1+nDots2) : double(totalGap);
  propList.insert("draw:distance", distance, librevenge::RVNG_POINT);;
}

void libfreehand::FHCollector::_appendArrowPath(librevenge::RVNGPropertyList &propList, const FHPath *arrow, bool startArrow)
{
  if (!arrow)
    return;

  FHPath path=*arrow;
  path.transform(FHTransform(0,-1,1,0,0,0));
  std::string pString=path.getPathString();
  if (pString.empty()) return;
  std::string wh(startArrow ? "start" : "end");
  propList.insert((std::string("draw:marker-")+wh+"-path").c_str(), pString.c_str());
  FHBoundingBox box;
  path.getBoundingBox(box.m_xmin, box.m_ymin, box.m_xmax, box.m_ymax);
  librevenge::RVNGString boxString;
  boxString.sprintf("%d %d %d %d", int(box.m_xmin*35), int(box.m_ymin*35), int(35*(box.m_xmax-box.m_xmin)), int(35*(box.m_ymax-box.m_ymin)));
  propList.insert((std::string("draw:marker-")+wh+"-viewbox").c_str(), boxString);
  propList.insert((std::string("draw:marker-")+wh+"-width").c_str(), 10, librevenge::RVNG_POINT); // change me
}

void libfreehand::FHCollector::_appendBasicLine(librevenge::RVNGPropertyList &propList, const libfreehand::FHBasicLine *basicLine)
{
  if (!basicLine)
    return;
  // osnola: we do not want to change draw:stroke, if stroke is defined recursively
  propList.insert("draw:stroke", "solid");
  librevenge::RVNGString color = getColorString(basicLine->m_colorId);
  if (!color.empty())
    propList.insert("svg:stroke-color", color);
  else if (!propList["svg:stroke-color"]) // set to default
    propList.insert("svg:stroke-color", "#000000");
  propList.insert("svg:stroke-width", basicLine->m_width);
  _appendLinePattern(propList, _findLinePattern(basicLine->m_linePatternId));
  _appendArrowPath(propList, _findArrowPath(basicLine->m_startArrowId), true);
  _appendArrowPath(propList, _findArrowPath(basicLine->m_endArrowId), false);
}

void libfreehand::FHCollector::_appendCustomProcLine(librevenge::RVNGPropertyList &propList, const libfreehand::FHCustomProc *customProc)
{
  if (!customProc)
    return;
  propList.insert("draw:stroke", "solid");
  librevenge::RVNGString color;
  if (!customProc->m_ids.empty())
    color= getColorString(customProc->m_ids[0]);
  if (!color.empty())
    propList.insert("svg:stroke-color", color);
  if (!customProc->m_widths.empty())
    propList.insert("svg:stroke-width", customProc->m_widths[0], librevenge::RVNG_POINT);
}

void libfreehand::FHCollector::_appendPatternLine(librevenge::RVNGPropertyList &propList, const libfreehand::FHPatternLine *patternLine)
{
  if (!patternLine)
    return;
  propList.insert("draw:stroke", "solid");
  librevenge::RVNGString color = getColorString(patternLine->m_colorId, patternLine->m_percentPattern);
  if (!color.empty())
    propList.insert("svg:stroke-color", color);
  else if (!propList["svg:stroke-color"]) // set to default
    propList.insert("svg:stroke-color", "#000000");
  propList.insert("svg:stroke-width", patternLine->m_width);
}

const libfreehand::FHPath *libfreehand::FHCollector::_findPath(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPath>::const_iterator iter = m_paths.find(id);
  if (iter != m_paths.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHNewBlend *libfreehand::FHCollector::_findNewBlend(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHNewBlend>::const_iterator iter = m_newBlends.find(id);
  if (iter != m_newBlends.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHGroup *libfreehand::FHCollector::_findGroup(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHGroup>::const_iterator iter = m_groups.find(id);
  if (iter != m_groups.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHGroup *libfreehand::FHCollector::_findClipGroup(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHGroup>::const_iterator iter = m_clipGroups.find(id);
  if (iter != m_clipGroups.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHCompositePath *libfreehand::FHCollector::_findCompositePath(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHCompositePath>::const_iterator iter = m_compositePaths.find(id);
  if (iter != m_compositePaths.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHPathText *libfreehand::FHCollector::_findPathText(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPathText>::const_iterator iter = m_pathTexts.find(id);
  if (iter != m_pathTexts.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHTextObject *libfreehand::FHCollector::_findTextObject(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHTextObject>::const_iterator iter = m_textObjects.find(id);
  if (iter != m_textObjects.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHTransform *libfreehand::FHCollector::_findTransform(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHTransform>::const_iterator iter = m_transforms.find(id);
  if (iter != m_transforms.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHTEffect *libfreehand::FHCollector::_findTEffect(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHTEffect>::const_iterator iter = m_tEffects.find(id);
  if (iter != m_tEffects.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHParagraph *libfreehand::FHCollector::_findParagraph(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHParagraph>::const_iterator iter = m_paragraphs.find(id);
  if (iter != m_paragraphs.end())
    return &(iter->second);
  return nullptr;
}

const std::vector<libfreehand::FHTab> *libfreehand::FHCollector::_findTabTable(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, std::vector<libfreehand::FHTab> >::const_iterator iter = m_tabs.find(id);
  if (iter != m_tabs.end())
    return &(iter->second);
  return nullptr;
}

const std::vector<unsigned> *libfreehand::FHCollector::_findTStringElements(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, std::vector<unsigned> >::const_iterator iter = m_tStrings.find(id);
  if (iter != m_tStrings.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHPropList *libfreehand::FHCollector::_findPropList(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPropList>::const_iterator iter = m_propertyLists.find(id);
  if (iter != m_propertyLists.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHGraphicStyle *libfreehand::FHCollector::_findGraphicStyle(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHGraphicStyle>::const_iterator iter = m_graphicStyles.find(id);
  if (iter != m_graphicStyles.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHBasicFill *libfreehand::FHCollector::_findBasicFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHBasicFill>::const_iterator iter = m_basicFills.find(id);
  if (iter != m_basicFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHLinearFill *libfreehand::FHCollector::_findLinearFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHLinearFill>::const_iterator iter = m_linearFills.find(id);
  if (iter != m_linearFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHLensFill *libfreehand::FHCollector::_findLensFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHLensFill>::const_iterator iter = m_lensFills.find(id);
  if (iter != m_lensFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHRadialFill *libfreehand::FHCollector::_findRadialFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHRadialFill>::const_iterator iter = m_radialFills.find(id);
  if (iter != m_radialFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHTileFill *libfreehand::FHCollector::_findTileFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHTileFill>::const_iterator iter = m_tileFills.find(id);
  if (iter != m_tileFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHPatternFill *libfreehand::FHCollector::_findPatternFill(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPatternFill>::const_iterator iter = m_patternFills.find(id);
  if (iter != m_patternFills.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHLinePattern *libfreehand::FHCollector::_findLinePattern(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHLinePattern>::const_iterator iter = m_linePatterns.find(id);
  if (iter != m_linePatterns.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHPath *libfreehand::FHCollector::_findArrowPath(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPath>::const_iterator iter = m_arrowPaths.find(id);
  if (iter != m_arrowPaths.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHBasicLine *libfreehand::FHCollector::_findBasicLine(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHBasicLine>::const_iterator iter = m_basicLines.find(id);
  if (iter != m_basicLines.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHCustomProc *libfreehand::FHCollector::_findCustomProc(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHCustomProc>::const_iterator iter = m_customProcs.find(id);
  if (iter != m_customProcs.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHPatternLine *libfreehand::FHCollector::_findPatternLine(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHPatternLine>::const_iterator iter = m_patternLines.find(id);
  if (iter != m_patternLines.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHRGBColor *libfreehand::FHCollector::_findRGBColor(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHRGBColor>::const_iterator iter = m_rgbColors.find(id);
  if (iter != m_rgbColors.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHTintColor *libfreehand::FHCollector::_findTintColor(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHTintColor>::const_iterator iter = m_tints.find(id);
  if (iter != m_tints.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHDisplayText *libfreehand::FHCollector::_findDisplayText(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHDisplayText>::const_iterator iter = m_displayTexts.find(id);
  if (iter != m_displayTexts.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHImageImport *libfreehand::FHCollector::_findImageImport(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHImageImport>::const_iterator iter = m_images.find(id);
  if (iter != m_images.end())
    return &(iter->second);
  return nullptr;
}

const librevenge::RVNGBinaryData *libfreehand::FHCollector::_findData(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, librevenge::RVNGBinaryData>::const_iterator iter = m_data.find(id);
  if (iter != m_data.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHSymbolClass *libfreehand::FHCollector::_findSymbolClass(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHSymbolClass>::const_iterator iter = m_symbolClasses.find(id);
  if (iter != m_symbolClasses.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHSymbolInstance *libfreehand::FHCollector::_findSymbolInstance(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHSymbolInstance>::const_iterator iter = m_symbolInstances.find(id);
  if (iter != m_symbolInstances.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FHFilterAttributeHolder *libfreehand::FHCollector::_findFilterAttributeHolder(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FHFilterAttributeHolder>::const_iterator iter = m_filterAttributeHolders.find(id);
  if (iter != m_filterAttributeHolders.end())
    return &(iter->second);
  return nullptr;
}

const std::vector<libfreehand::FHColorStop> *libfreehand::FHCollector::_findMultiColorList(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, std::vector<libfreehand::FHColorStop> >::const_iterator iter = m_multiColorLists.find(id);
  if (iter != m_multiColorLists.end())
    return &(iter->second);
  return nullptr;
}

const double *libfreehand::FHCollector::_findOpacityFilter(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, double>::const_iterator iter = m_opacityFilters.find(id);
  if (iter != m_opacityFilters.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FWShadowFilter *libfreehand::FHCollector::_findFWShadowFilter(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FWShadowFilter>::const_iterator iter = m_shadowFilters.find(id);
  if (iter != m_shadowFilters.end())
    return &(iter->second);
  return nullptr;
}

const libfreehand::FWGlowFilter *libfreehand::FHCollector::_findFWGlowFilter(unsigned id)
{
  if (!id)
    return nullptr;
  std::map<unsigned, FWGlowFilter>::const_iterator iter = m_glowFilters.find(id);
  if (iter != m_glowFilters.end())
    return &(iter->second);
  return nullptr;
}

unsigned libfreehand::FHCollector::_findStrokeId(const libfreehand::FHGraphicStyle &graphicStyle)
{
  unsigned listId = graphicStyle.m_attrId;
  if (!listId)
    return 0;
  std::map<unsigned, FHList>::const_iterator iter = m_lists.find(listId);
  if (iter == m_lists.end())
    return 0;
  unsigned strokeId = 0;
  for (unsigned int element : iter->second.m_elements)
  {
    unsigned valueId = _findValueFromAttribute(element);
    if (_findBasicLine(valueId))
      strokeId = valueId;
  }
  return strokeId;
}

unsigned libfreehand::FHCollector::_findFillId(const libfreehand::FHGraphicStyle &graphicStyle)
{
  unsigned listId = graphicStyle.m_attrId;
  if (!listId)
    return 0;
  std::map<unsigned, FHList>::const_iterator iter = m_lists.find(listId);
  if (iter == m_lists.end())
    return 0;
  unsigned fillId = 0;
  for (unsigned int element : iter->second.m_elements)
  {
    unsigned valueId = _findValueFromAttribute(element);
    // Add other fills if we support them
    if (_findBasicFill(valueId) || _findLinearFill(valueId)
        || _findLensFill(valueId) || _findRadialFill(valueId)
        || _findTileFill(valueId) || _findPatternFill(valueId) || _findCustomProc(valueId))
      fillId = valueId;
  }
  return fillId;
}

const libfreehand::FHFilterAttributeHolder *libfreehand::FHCollector::_findFilterAttributeHolder(const libfreehand::FHGraphicStyle &graphicStyle)
{
  unsigned listId = graphicStyle.m_attrId;
  if (!listId)
    return nullptr;
  std::map<unsigned, FHList>::const_iterator iter = m_lists.find(listId);
  if (iter == m_lists.end())
    return nullptr;
  for (unsigned int element : iter->second.m_elements)
  {
    const FHFilterAttributeHolder *attributeHolder = _findFilterAttributeHolder(element);

    if (attributeHolder)
      return attributeHolder;
  }
  return nullptr;
}


unsigned libfreehand::FHCollector::_findValueFromAttribute(unsigned id)
{
  if (!id)
    return 0;
  std::map<unsigned, FHAttributeHolder>::const_iterator iter = m_attributeHolders.find(id);
  if (iter == m_attributeHolders.end())
    return 0;
  unsigned value = 0;
  if (iter->second.m_parentId)
    value = _findValueFromAttribute(iter->second.m_parentId);
  if (iter->second.m_attrId)
    value = iter->second.m_attrId;
  return value;
}

librevenge::RVNGBinaryData libfreehand::FHCollector::getImageData(unsigned id)
{
  std::map<unsigned, FHDataList>::const_iterator iter = m_dataLists.find(id);
  librevenge::RVNGBinaryData data;
  if (iter == m_dataLists.end())
    return data;
  for (unsigned int element : iter->second.m_elements)
  {
    const librevenge::RVNGBinaryData *pData = _findData(element);
    if (pData)
      data.append(*pData);
  }
  return data;
}

librevenge::RVNGString libfreehand::FHCollector::getColorString(unsigned id, double tintVal)
{
  FHRGBColor col;
  const FHRGBColor *color = _findRGBColor(id);
  if (color)
    col=*color;
  else
  {
    const FHTintColor *tint = _findTintColor(id);
    if (!tint)
      return librevenge::RVNGString();
    col=getRGBFromTint(*tint);
  }
  if (tintVal<=0 || tintVal>=1)
    return _getColorString(col);

  FHRGBColor finalColor;
  finalColor.m_red = col.m_red * tintVal + (1 - tintVal) * 65536;
  finalColor.m_green = col.m_green * tintVal + (1 - tintVal) * 65536;
  finalColor.m_blue = col.m_blue * tintVal + (1 - tintVal) * 65536;
  return _getColorString(finalColor);
}

libfreehand::FHRGBColor libfreehand::FHCollector::getRGBFromTint(const FHTintColor &tint)
{
  if (!tint.m_baseColorId)
    return FHRGBColor();
  const FHRGBColor *rgbColor = _findRGBColor(tint.m_baseColorId);
  if (!rgbColor)
    return FHRGBColor();
  unsigned red = rgbColor->m_red * tint.m_tint + ((65536 - tint.m_tint) << 16);
  unsigned green = rgbColor->m_green * tint.m_tint + ((65536 - tint.m_tint) << 16);
  unsigned blue = rgbColor->m_blue * tint.m_tint + ((65536 - tint.m_tint) << 16);
  FHRGBColor color;
  color.m_red = (red >> 16);
  color.m_green = (green >> 16);
  color.m_blue = (blue >> 16);
  return color;
}

void libfreehand::FHCollector::_generateBitmapFromPattern(librevenge::RVNGBinaryData &bitmap, unsigned colorId, const std::vector<unsigned char> &pattern)
{
  unsigned height = 8;
  unsigned width = 8;
  unsigned tmpPixelSize = (unsigned)(height * width);

  unsigned tmpDIBImageSize = tmpPixelSize * 4;

  unsigned tmpDIBOffsetBits = 14 + 40;
  unsigned tmpDIBFileSize = tmpDIBOffsetBits + tmpDIBImageSize;

  // Create DIB file header
  writeU16(bitmap, 0x4D42);  // Type
  writeU32(bitmap, (int)tmpDIBFileSize); // Size
  writeU16(bitmap, 0); // Reserved1
  writeU16(bitmap, 0); // Reserved2
  writeU32(bitmap, (int)tmpDIBOffsetBits); // OffsetBits

  // Create DIB Info header
  writeU32(bitmap, 40); // Size

  writeU32(bitmap, (int)width);  // Width
  writeU32(bitmap, (int)height); // Height

  writeU16(bitmap, 1); // Planes
  writeU16(bitmap, 32); // BitCount
  writeU32(bitmap, 0); // Compression
  writeU32(bitmap, (int)tmpDIBImageSize); // SizeImage
  writeU32(bitmap, 0); // XPelsPerMeter
  writeU32(bitmap, 0); // YPelsPerMeter
  writeU32(bitmap, 0); // ColorsUsed
  writeU32(bitmap, 0); // ColorsImportant

  unsigned foreground = 0x000000; // Initialize to black and override after
  unsigned background = 0xffffff; // Initialize to white, since that is Freehand behaviour even if overlapping other colors

  const FHRGBColor *color = _findRGBColor(colorId);
  if (color)
    foreground = ((unsigned)(color->m_red & 0xff00) << 8)|((unsigned)color->m_green & 0xff00)|((unsigned)color->m_blue >> 8);
  else
  {
    const FHTintColor *tintColor = _findTintColor(colorId);
    if (tintColor)
    {
      FHRGBColor rgbColor = getRGBFromTint(*tintColor);
      foreground = ((unsigned)(rgbColor.m_red & 0xff00) << 8)|((unsigned)rgbColor.m_green & 0xff00)|((unsigned)rgbColor.m_blue >> 8);
    }
  }
  for (unsigned j = height; j > 0; --j)
  {
    unsigned char c(pattern[j-1]);
    for (unsigned i = 0; i < width; ++i)
    {
      if (c & 0x80)
        writeU32(bitmap, foreground);
      else
        writeU32(bitmap, background);
      c <<= 1;
    }
  }
}


/* vim:set shiftwidth=2 softtabstop=2 expandtab: */