// =============================================================== //
//                                                                 //
//   File      : AW_print.cxx                                      //
//   Purpose   :                                                   //
//                                                                 //
//   Institute of Microbiology (Technical University Munich)       //
//   http://www.arb-home.de/                                       //
//                                                                 //
// =============================================================== //

#include "aw_root.hxx"
#include "aw_common.hxx"
#include "aw_xfont.hxx"
#include "aw_rgb.hxx"

#include <arb_msg.h>

#include <map>
#include <list>
#include <vector>
#include <string>

using namespace AW;
using namespace std;

#define XFIG_DEFAULT_COLOR_COUNT 32  // colors "hardcoded" in fig format
#define XFIG_USER_COLOR_COUNT    512 // restriction defined by fig format

#define XFIG_USER_COLOR_FIRST XFIG_DEFAULT_COLOR_COUNT
#define XFIG_USER_COLOR_LAST  (XFIG_USER_COLOR_FIRST+XFIG_USER_COLOR_COUNT-1)

// -------------------
//      Spoolable

struct Spoolable {
    virtual ~Spoolable() {}

    virtual const char *text() const  = 0;
    virtual const AW_rgb *rgb() const = 0;
};

class SpoolableString : public Spoolable {
    string s;
public:
    SpoolableString(const char *txt) : s(txt) {}

    const char *text() const OVERRIDE { return s.c_str(); }
    const AW_rgb *rgb() const OVERRIDE { return NULp; }
};
class SpoolableColor : public Spoolable {
    AW_rgb color;
public:
    SpoolableColor(const AW_rgb& color_) : color(color_) {}

    const char *text() const OVERRIDE { return NULp; }
    const AW_rgb *rgb() const OVERRIDE { return &color; }
};

typedef SmartPtr<Spoolable> SpoolablePtr;

// ---------------------
//      color mapping

static AW_rgb figStdColors[32] = {
    // color names were taken from xfig color chooser.
    // corresponding color values were taken from X11 source code:
    // https://cgit.freedesktop.org/xorg/xserver/tree/os/oscolor.c

    0x000000, // Black [index 0]
    0x0000ff, // Blue
    0x00ff00, // Green
    0x00ffff, // Cyan
    0xff0000, // Red
    0xff00ff, // Magenta
    0xffff00, // Yellow
    0xffffff, // White
    0x00008b, // Blue4
    0x0000cd, // Blue3
    0x0000ee, // Blue2
    0xadd8e6, // LtBlue
    0x008b00, // Green4
    0x00cd00, // Green3
    0x00ee00, // Green2
    0x008b8b, // Cyan4
    0x00cdcd, // Cyan3
    0x00eeee, // Cyan2
    0x8b0000, // Red4
    0xcd0000, // Red3
    0xee0000, // Red2
    0x8b008b, // Magenta4
    0xcd00cd, // Magenta3
    0xee00ee, // Magenta2
    0x8b2323, // Brown4
    0xcd3333, // Brown3
    0xee3b3b, // Brown2
    0x8b636c, // Pink4
    0xcd919e, // Pink3
    0xeea9b8, // Pink2
    0xffc0cb, // Pink
    0xffd700, // Gold [index 31]
};

class UsedColor {
    int count; // how often color occurred (=weight)
    int index; // xfig color index (-2 = not assigned; -1 .. 31 = fixed xfig color; 32..543 = user defined color)

    static const int NOT_ASSIGNED = -2;

public:
    UsedColor() : count(0), index(NOT_ASSIGNED) {}
    explicit UsedColor(int idx) : count(0), index(idx) {} // used to add figStdColors

    void inc() { ++count; }
    void addCount(int toAdd) { count += toAdd; }
    int getCount() const { return count; }

    bool isAssigned() const { return index != NOT_ASSIGNED; }
    bool isStdColor() const { return index>=0 && index<XFIG_USER_COLOR_FIRST; }

    void assign(int idx) {
        aw_assert(!isAssigned());
        aw_assert(idx>=XFIG_USER_COLOR_FIRST && idx<=XFIG_USER_COLOR_LAST); // only allow to assign user colors
        index = idx;
        aw_assert(isAssigned());
    }

    int getIndex() const { aw_assert(isAssigned()); return index; }
};

inline double colorDistSquare(const AW_rgb& col1, const AW_rgb& col2) {
    AW_rgb_diff diff = AW_rgb_normalized(AW_rgb16(col1)) - AW_rgb_normalized(AW_rgb16(col2));
    return diff.r()*diff.r() + diff.g()*diff.g() + diff.b()*diff.b();
}

class ColorMap {
    // maps RGB values to xfig color indices
    typedef map<AW_rgb,UsedColor> ColMap;
    typedef map<AW_rgb,AW_rgb>    Remap; // colors -> replaced by different color

    ColMap color;
    Remap  replaced;

    AW_rgb findCheapestReplaceableColor(AW_rgb *replaceBy) {
        AW_rgb cheapest = AW_NO_COLOR;
        double penalty  = numeric_limits<float>::max();

        for (ColMap::const_iterator i = color.begin(); i != color.end(); ++i) {
            const AW_rgb& col1 = i->first;
            if (i->second.isStdColor()) continue; // do not attempt to replace standard colors

            const int amount = i->second.getCount();
            aw_assert(amount>0); // otherwise color should not appear in table

            for (ColMap::const_iterator j = color.begin(); j != color.end(); ++j) {
                const AW_rgb& col2 = j->first;

                if (&col1 != &col2) {
                    double currPenalty = amount*colorDistSquare(col1, col2);
#if defined(DEBUG) && 0
                    fprintf(stderr, "replace %s ", AW_rgb16(col1).ascii());
                    fprintf(stderr, "by %s (amount=%i) -> penalty = %f\n", AW_rgb16(col2).ascii(), amount, currPenalty);
#endif

                    if (currPenalty<penalty) {
                        penalty    = currPenalty;
                        cheapest   = col1;
                        *replaceBy = col2;
                    }
                }
            }
        }

        aw_assert(penalty<numeric_limits<float>::max()); // no color found
        aw_assert(penalty>0); // should not occur!
        return cheapest;
    }

public:
    ColorMap() {
        // add fig standard colors to ColMap [=color indices 0..31]
        for (int i = 0; i<XFIG_DEFAULT_COLOR_COUNT; ++i) {
            color[figStdColors[i]] = UsedColor(i);
        }
    }

    void add(AW_rgb rgb) { color[rgb].inc(); }

    bool operator()(const AW_rgb& c1, const AW_rgb& c2) const {
        // operator to sort colors (often used colors first; strict order)
        ColMap::const_iterator f1 = color.find(c1);
        ColMap::const_iterator f2 = color.find(c2);

        aw_assert(f1 != color.end());
        aw_assert(f2 != color.end());

        int cmp = f1->second.getCount() - f2->second.getCount();
        if (!cmp) cmp = c1-c2; // define strict order using rgb value
        return cmp>0;
    }

    void mapColors(size_t allowed) {
        size_t usedColors = color.size(); // with std-colors

        allowed += XFIG_DEFAULT_COLOR_COUNT;

        // reduce colors if too many colors in map:
        while (usedColors>allowed) {
            AW_rgb replaceBy;
            AW_rgb cheapest = findCheapestReplaceableColor(&replaceBy);

            aw_assert(!color[cheapest].isStdColor());

            // @@@ if amount of 'cheapest' and 'replaceBy' is (nearly) the same -> create mixed color and replace both?

            replaced[cheapest] = replaceBy; // store color replacement
            color[replaceBy].addCount(color[cheapest].getCount()); // add occurrences of removed color to replacement color

            IF_ASSERTION_USED(size_t erased = )color.erase(cheapest);
            aw_assert(erased == 1);

            --usedColors;
        }

        aw_assert(color.size() <= allowed); // failed to reduce colors

        vector<AW_rgb> sortedColors;
        for (ColMap::iterator i = color.begin(); i != color.end(); ++i) {
            sortedColors.push_back(i->first);
        }
        sort(sortedColors.begin(), sortedColors.end(), *this);

        // stupid mapping:
        int nextIdx = XFIG_USER_COLOR_FIRST;
        for (ColMap::iterator i = color.begin(); i != color.end(); ++i) {
            UsedColor& used = i->second;
            if (!used.isStdColor()) {
                used.assign(nextIdx++);
            }
        }
    }

    void printUserColorDefinitions(FILE *xout) {
        const AW_rgb *used[XFIG_USER_COLOR_LAST+1];

        for (int i = 0; i<=XFIG_USER_COLOR_LAST; ++i) {
            used[i] = NULp;
        }

        for (ColMap::const_iterator i = color.begin(); i != color.end(); ++i) {
#if defined(ASSERTION_USED)
            const AW_rgb& rgb = i->first;
            aw_assert(rgb != AW_NO_COLOR);
#endif
            const UsedColor& ucol = i->second;
            aw_assert(ucol.isAssigned());

            int idx   = ucol.getIndex();
            aw_assert(!used[idx]);
            used[idx] = &(i->first);
        }

        // print custom color definitions sorted by index:
        for (int i = XFIG_USER_COLOR_FIRST; i<=XFIG_USER_COLOR_LAST; ++i) {
            if (used[i]) {
                fprintf(xout, "0 %d #%06lx\n", i, *used[i]);
            }
        }
    }

    int rgb2index(AW_rgb rgb) const {
        ColMap::const_iterator found = color.find(rgb);
        while (found == color.end()) {
            Remap::const_iterator remapped = replaced.find(rgb);
            aw_assert(remapped != replaced.end());

            rgb   = remapped->second;
            found = color.find(rgb);
        }
        return found->second.getIndex();
    }
};

// -----------------
//      SPOOLER

class SPOOLER {
    // SPOOLER delays output of xfig objects:
    // 1. collect object code (while tracing information about used colors)
    // 2. generate color table (probably reduced)
    // 3. print object code (and remap colors)

    typedef list<SpoolablePtr> Spoolables;

    Spoolables objs;

    void initColors(ColorMap& colmap) {
        for (Spoolables::const_iterator i = objs.begin(); i != objs.end(); ++i) {
            const AW_rgb *rgb = (*i)->rgb();
            if (rgb) colmap.add(*rgb);
        }
    }

public:
    SPOOLER() {}

    void put(const char *str) { objs.push_back(new SpoolableString(str)); }
    void putColor(AW_rgb rgb) { objs.push_back(new SpoolableColor(rgb)); }
    void putDefaultColor() { put("-1 "); }

    void spool(FILE *xout) {
        ColorMap colmap;

        initColors(colmap);
        colmap.mapColors(XFIG_USER_COLOR_COUNT);
        colmap.printUserColorDefinitions(xout);

        while (!objs.empty()) {
            SpoolablePtr next = objs.front();
            objs.pop_front();

            const char *txt = next->text();
            if (txt) {
                fputs(txt, xout);
            }
            else {
                const AW_rgb *rgb = next->rgb();
                arb_assert(rgb);

                int idx    = colmap.rgb2index(*rgb);
                fprintf(xout, "%d ", idx);
            }
        }
    }

};

// -------------------------
//      AW_device_print

const double dpi_screen2printer = double(DPI_PRINTER)/DPI_SCREEN;

inline double screen2printer(double val) { return val*dpi_screen2printer; }
inline int print_pos(AW_pos screen_pos) { return AW_INT(screen2printer(screen_pos)); }

AW_DEVICE_TYPE AW_device_print::type() { return AW_DEVICE_PRINTER; }

// Note: parameters to spoolf HAVE TO be wrapped in "()"!
#define spoolf(format)      spooler->put(GBS_global_string format)
#define spools(str)         spooler->put(str)
#define spoolColor(rgb)     color_mode ? spooler->putColor(rgb) : spooler->putDefaultColor()
#define spoolDefaultColor() spooler->putDefaultColor()

bool AW_device_print::line_impl(int gc, const LineVector& Line, AW_bitset filteri) {
    bool drawflag = false;
    if (filteri & filter) {
        LineVector transLine = transform(Line);
        LineVector clippedLine;
        drawflag = clip(transLine, clippedLine);

        if (drawflag) {
            const AW_GC *gcm        = get_common()->map_gc(gc);
            int          line_width = gcm->get_line_width();

            int    line_mode = 0;
            double gap_ratio = 0.0;
            switch (gcm->get_line_style()) {
                case AW_SOLID: /* use defaults from above*/ break;
                case AW_DASHED:  line_mode = 1; gap_ratio = 4.0; break;
                case AW_DOTTED:  line_mode = 2; gap_ratio = 2.0; break;
            }

            aw_assert(xfig); // file has to be good!

            // type, subtype, style, thickness, pen_color,
            // fill_color(new), depth, pen_style, area_fill, style_val,
            // join_style(new), cap_style(new), radius, forward_arrow,
            // backward_arrow, npoints
            spoolf(("2 1 %d %d ",
                    line_mode,
                    AW_INT(line_width)));
            spoolColor(gcm->get_last_fg_color());
            spoolf(("0 0 0 0 %5.3f 0 1 0 0 0 2\n\t%d %d %d %d\n",
                    gap_ratio,
                    print_pos(clippedLine.xpos()),
                    print_pos(clippedLine.ypos()),
                    print_pos(clippedLine.head().xpos()),
                    print_pos(clippedLine.head().ypos())));
        }
    }
    return drawflag;
}

bool AW_device_print::invisible_impl(const AW::Position& pos, AW_bitset filteri) {
    bool drawflag = false;
    if (filteri & filter) {
        Position trans = transform(pos);

        drawflag = !is_outside_clip(trans);
        if (drawflag) {
            spoolf(("2 1 0 1 7 7 50 -1 -1 0.000 0 0 -1 0 0 1\n\t%d %d\n",
                    print_pos(trans.xpos()),
                    print_pos(trans.ypos())));
        }
    }
    return drawflag;
}

void AW_device_print::draw_text(int gc, const char *textBuffer, size_t textStart, size_t textLen, const AW::Position& pos) {
    const AW_GC *gcm = get_common()->map_gc(gc);

    AW::Position POS(transform(pos));

    char *pstr                                = strdup(textBuffer+textStart); // @@@ only copy textLen characters!
    if (textLen < strlen(pstr)) pstr[textLen] = 0; // @@@ caller asserts textLen <= strlen(pstr)
    else  textLen                             = strlen(pstr);

    size_t i;
    for (i=0; i<textLen; i++) {
        if (pstr[i] < ' ') pstr[i] = '?';
    }

    int fontnr           = AW_font_2_xfig(gcm->get_fontnr());
    if (fontnr<0) fontnr = - fontnr;
    if (textBuffer[0]) { // @@@ incorrect or useless?
        // 4=string 0=left color depth penstyle font font_size angle
        // font_flags height length x y string
        // (font/fontsize and color/depth have been switched from format 2.1 to 3.2)

        SizedCstr sstr(textBuffer); // @@@ shouldn't this use only part of textBuffer??? maybe 'pstr'?

        spools("4 0 ");
        spoolColor(gcm->get_last_fg_color());
        spoolf(("0 0 %d %d 0.000 4 %d %d %d %d ",
                fontnr,
                gcm->get_fontsize(),
                (int)gcm->get_font_limits().get_height(),
                gcm->get_string_size(sstr),
                print_pos(POS.xpos()),
                print_pos(POS.ypos())));

        spoolf(("%s\\001\n", pstr));
    }
    free(pstr);
}

GB_ERROR AW_device_print::open(const char *path) {
    if (xfig) return "You cannot reopen a device";

    xfig = fopen(path, "w");
    if (!xfig) return GB_IO_error("writing", path);

    fprintf(xfig,
            "#FIG 3.2\n"   // version
            "Landscape\n"  // "Portrait"
            "Center\n"     // "Flush Left"
            "Metric\n"     // "Inches"
            "A4\n"         // papersize
            "100.0\n"      // export&print magnification %
            "Single\n"     // Single/Multiple Pages
            "-3\n"         // background = transparent for gif export
            "%i 2\n"       // dpi, 2  = origin in upper left corner
            , DPI_PRINTER);

    spooler = new SPOOLER;

    return NULp;
}

int AW_common::find_data_color_idx(AW_rgb color) const {
    for (int i=0; i<data_colors_size; i++) {
        if (color == data_colors[i]) {
            return i;
        }
    }
    return -1;
}

int AW_device_print::find_color_idx(AW_rgb color) {
    int idx = -1;
    if (color_mode) {
        idx = get_common()->find_data_color_idx(color);
        if (idx >= 0) idx += XFIG_USER_COLOR_FIRST;
    }
    return idx;
}

void AW_device_print::close() {
    arb_assert(correlated(spooler, xfig));

    if (spooler) {
        spooler->spool(xfig);

        delete spooler;
        spooler = NULp;

        fclose(xfig);
        xfig = NULp;
    }
}

static bool AW_draw_string_on_printer(AW_device *device, int gc, const char *textBuffer, size_t textStart, size_t textLen, const AW::Position& pos, AW_CL /*cduser*/) {
    DOWNCAST(AW_device_print*, device)->draw_text(gc, textBuffer, textStart, textLen, pos);
    return true;
}
bool AW_device_print::text_impl(int gc, const SizedCstr& cstr, const AW::Position& pos, AW_pos alignment, AW_bitset filteri) {
    return text_overlay(gc, cstr, pos, alignment, filteri, AW_draw_string_on_printer);
}

bool AW_device_print::box_impl(int gc, AW::FillStyle filled, const Rectangle& rect, AW_bitset filteri) {
    bool drawflag = false;
    if (filter & filteri) {
        if (filled.somehow()) {
            Position q[4];
            q[0] = rect.upper_left_corner();
            q[1] = rect.upper_right_corner();
            q[2] = rect.lower_right_corner();
            q[3] = rect.lower_left_corner();

            drawflag = polygon(gc, filled, 4, q, filteri);
        }
        else {
            drawflag = generic_box(gc, rect, filteri);
        }
    }
    return drawflag;
}

AW_device::Fill_Style AW_device_print::detectFillstyleForGreylevel(int gc, AW::FillStyle filled) { // @@@ directly pass greylevel (or AW_GC?)
    // @@@ DRY code here vs setFillstyleForGreylevel

    switch (filled.get_style()) {
        case AW::FillStyle::SOLID: return FS_SOLID;
        case AW::FillStyle::EMPTY: return FS_EMPTY;

        case AW::FillStyle::SHADED:
        case AW::FillStyle::SHADED_WITH_BORDER:
            break; // detect using greylevel
    }

    AW_grey_level greylevel = get_grey_level(gc);

    static const float PART = 1.0/22; // exact for color_mode==false

    if (greylevel<PART) return FS_EMPTY;
    if (greylevel>(1.0-PART)) return FS_SOLID;

    return FS_GREY;
}

int AW_device_print::calcAreaFill(AW_device::Fill_Style fillStyle, AW_grey_level grey_level) {
    int area_fill = -1;
    if (fillStyle == FS_SOLID) { // @@@ use switch here
        area_fill = 20;
    }
    else if (fillStyle != FS_EMPTY) {
        aw_assert(grey_level>=0.0 && grey_level<=1.0);
        if (color_mode) {
            area_fill = AW_INT(40-20*grey_level); // 20 = full saturation; 40 = white
            aw_assert(area_fill != 40); // @@@ should better use fillStyle==FS_EMPTY in that case
        }
        else {
            area_fill = AW_INT(20*grey_level); // 1=light 19=dark 20=solid
            aw_assert(area_fill != 0); // @@@ should better use fillStyle==FS_EMPTY in that case
        }
        aw_assert(area_fill != 20); // @@@ should better use fillStyle==FS_SOLID in that case
    }
    return area_fill;
}

bool AW_device_print::circle_impl(int gc, AW::FillStyle filled, const Position& center, const AW::Vector& radius, AW_bitset filteri) {
    bool drawflag = false;
    if (filteri & filter) {
        aw_assert(radius.x()>0 && radius.y()>0);
        Rectangle Box(center-radius, center+radius);
        Rectangle screen_box = transform(Box);
        Rectangle clipped_box;
        drawflag          = box_clip(screen_box, clipped_box);
        bool half_visible = (clipped_box.surface()*2) > screen_box.surface();

        drawflag = drawflag && half_visible;
        // @@@ correct behavior would be to draw an arc if only partly visible

        if (drawflag) {
            const AW_GC *gcm = get_common()->map_gc(gc);

            // force into clipped_box (hack):
            Position Center        = clipped_box.centroid();
            Vector   screen_radius = clipped_box.diagonal()/2;

            int cx = print_pos(Center.xpos());
            int cy = print_pos(Center.ypos());
            int rx = print_pos(screen_radius.x());
            int ry = print_pos(screen_radius.y());

            {
                int subtype = (rx == ry) ? 3 : 1; // 3(circle) 1(ellipse)
                subtype     = 3; // @@@ remove after refactoring
                spoolf(("1 %d  ", subtype));  // type + subtype:
            }

            {
                int line_width = gcm->get_line_width();
                spoolf(("%d %d ", AW_SOLID, line_width));   // line_style + line_width

                {
                    AW_rgb     color     = gcm->get_last_fg_color();
                    Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled);
                    int        area_fill = calcAreaFill(fillStyle, gcm->get_grey_level());

                    spoolColor(color); // pen_color
                    if (area_fill == -1) {
                        spoolDefaultColor(); // fill color
                    }
                    else {
                        spoolColor(color); // fill color
                    }
                    spoolf(("0 0 %d ", area_fill)); // depth + pen_style + area_fill
                }
                spoolf(("0.000 1 0.0000 "));                // style_val + direction + angle (x-axis)
            }

            spoolf(("%d %d ", cx, cy)); // center
            spoolf(("%d %d ", rx, ry)); // radius
            spoolf(("%d %d ", cx, cy)); // start
            spoolf(("%d %d\n", print_pos(Center.xpos()+screen_radius.x()), cy)); // end
        }
    }
    return drawflag;
}

bool AW_device_print::arc_impl(int gc, AW::FillStyle filled, const AW::Position& center, const AW::Vector& radius, int start_degrees, int arc_degrees, AW_bitset filteri) {
    bool drawflag = false;
    if (filteri && filter) {
        aw_assert(radius.x()>0 && radius.y()>0);
        Rectangle Box(center-radius, center+radius);
        Rectangle screen_box = transform(Box);
        Rectangle clipped_box;
        drawflag          = box_clip(screen_box, clipped_box);
        bool half_visible = (clipped_box.surface()*2) > screen_box.surface();

        drawflag = drawflag && half_visible;
        // @@@ correct behavior would be to draw an arc if only partly visible

        UNCOVERED(); // @@@ AW_device_print::arc_impl not triggered from test code.
                     // Uses via method arc():
                     // - from SECEDIT to draw '~'-bonds (may be saved to xfig)
                     // - from EDIT4 to mark protein codon triples (NEVER saved to xfig)
                     // AW_device_Xm version is used indirectly via method circle_impl().

        if (drawflag) {
            const AW_GC *gcm = get_common()->map_gc(gc);

            // force into clipped_box (hack):
            Position Center        = clipped_box.centroid();
            Vector   screen_radius = clipped_box.diagonal()/2;

            int cx = print_pos(Center.xpos());
            int cy = print_pos(Center.ypos());
            int rx = print_pos(screen_radius.x());
            int ry = print_pos(screen_radius.y());

            bool use_spline = (rx != ry); // draw interpolated spline for ellipsoid arcs

            spools(use_spline ? "3 4 " : "5 1 ");  // type + subtype:

            {
                int line_width = gcm->get_line_width();
                spoolf(("%d %d ", AW_SOLID, line_width));   // line_style + line_width

                {
                    AW_rgb     color     = gcm->get_last_fg_color();
                    Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled);
                    int        area_fill = calcAreaFill(fillStyle, gcm->get_grey_level());

                    spoolColor(color); // pen_color
                    if (area_fill == -1) {
                        spoolDefaultColor(); // fill color
                    }
                    else {
                        spoolColor(color); // fill color
                    }
                    spoolf(("0 0 %d ", area_fill)); // depth + pen_style + area_fill
#if defined(UNIT_TESTS)
                    if (area_fill != -1) { UNCOVERED(); } // @@@ filled arcs not tested
                    else                 { UNCOVERED(); } // @@@ unfilled arcs not tested
#endif
                }
                spools("0.000 1 ");                         // style_val + cap_style
                if (!use_spline) spools("1 ");              // direction
                spools("0 0 ");                             // 2 arrows
            }

            Angle a0(Angle::deg2rad*start_degrees);
            Angle a1(Angle::deg2rad*(start_degrees+arc_degrees));

            if (use_spline) {
                const int MAX_ANGLE_STEP = 45; // degrees

                int steps = (abs(arc_degrees)-1)/MAX_ANGLE_STEP+1;
                Angle step(Angle::deg2rad*double(arc_degrees)/steps);

                spoolf(("%d\n\t", steps+1)); // npoints

                double rmax, x_factor, y_factor;
                if (rx>ry) {
                    rmax     = rx;
                    x_factor = 1.0;
                    y_factor = double(ry)/rx;
                }
                else {
                    rmax     = ry;
                    x_factor = double(rx)/ry;
                    y_factor = 1.0;
                }

                for (int n = 0; n <= steps; ++n) {
                    Vector   toCircle  = a0.normal()*rmax;
                    Vector   toEllipse(toCircle.x()*x_factor, toCircle.y()*y_factor);
                    Position onEllipse = Center+toEllipse;

                    int x = print_pos(onEllipse.xpos());
                    int y = print_pos(onEllipse.ypos());

                    spoolf((" %d %d", x, y));

                    if (n<steps) {
                        if (n == steps-1) a0 = a1;
                        else              a0 += step;
                    }
                }
                spools("\n\t");
                for (int n = 0; n <= steps; ++n) {
                    // -1 = interpolate; 0 = discontinuity; 1 = approximate
                    spoolf((" %d", (n == 0 || n == steps) ? 0 : -1));
                }
                spools("\n");
            }
            else {
                spoolf(("%d %d ", cx, cy)); // center

                Angle am(Angle::deg2rad*(start_degrees+arc_degrees*0.5));

                double   r  = screen_radius.x();
                Position p0 = Center+a0.normal()*r;
                Position pm = Center+am.normal()*r;
                Position p1 = Center+a1.normal()*r;

                spoolf(("%d %d ",  print_pos(p0.xpos()), print_pos(p0.ypos())));
                spoolf(("%d %d ",  print_pos(pm.xpos()), print_pos(pm.ypos())));
                spoolf(("%d %d\n", print_pos(p1.xpos()), print_pos(p1.ypos())));
            }
        }
    }
    return drawflag;
}

bool AW_device_print::polygon_impl(int gc, AW::FillStyle filled, int npos, const Position *pos, AW_bitset filteri) {
    bool drawflag = false;
    if (filter & filteri) {
        drawflag = generic_polygon(gc, npos, pos, filteri);
        if (drawflag) { // line visible -> area fill needed
            const AW_GC *gcm = get_common()->map_gc(gc);

            int line_width = gcm->get_line_width();

            spools("2 3 ");
            spoolf(("%d %d ", AW_SOLID, line_width));

            {
                AW_rgb     color     = gcm->get_last_fg_color();
                Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled);
                int        area_fill = calcAreaFill(fillStyle, gcm->get_grey_level());

                spoolColor(color); // pen_color
                if (area_fill == -1) {
                    spoolDefaultColor(); // fill color
                }
                else {
                    spoolColor(color); // fill color
                }
                spoolf(("0 0 %d ", area_fill));             // depth + pen_style + area_fill
            }

            spools("0.000 0 0 -1 0 0 ");

            spoolf(("%d\n", npos+1));

            // @@@ method used here for clipping leads to wrong results,
            // since group border (drawn by generic_polygon() above) is clipped correctly,
            // but filled content is clipped different.
            //
            // fix: clip the whole polygon before drawing border

            for (int i=0; i <= npos; i++) {
                int j = i == npos ? 0 : i; // print pos[0] after pos[n-1]

                Position transPos = transform(pos[j]);
                Position clippedPos;
                ASSERT_RESULT(bool, true, force_into_clipbox(transPos, clippedPos));
                spoolf(("   %d %d\n", print_pos(clippedPos.xpos()), print_pos(clippedPos.ypos())));
            }
        }
    }
    return drawflag;
}
