| 1 | // =============================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : AW_print.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // =============================================================== // |
|---|
| 10 | |
|---|
| 11 | #include "aw_root.hxx" |
|---|
| 12 | #include "aw_common.hxx" |
|---|
| 13 | #include "aw_xfont.hxx" |
|---|
| 14 | #include "aw_rgb.hxx" |
|---|
| 15 | |
|---|
| 16 | #include <arb_msg.h> |
|---|
| 17 | |
|---|
| 18 | #include <map> |
|---|
| 19 | #include <list> |
|---|
| 20 | #include <vector> |
|---|
| 21 | #include <string> |
|---|
| 22 | |
|---|
| 23 | using namespace AW; |
|---|
| 24 | using namespace std; |
|---|
| 25 | |
|---|
| 26 | #define XFIG_DEFAULT_COLOR_COUNT 32 // colors "hardcoded" in fig format |
|---|
| 27 | #define XFIG_USER_COLOR_COUNT 512 // restriction defined by fig format |
|---|
| 28 | |
|---|
| 29 | #define XFIG_USER_COLOR_FIRST XFIG_DEFAULT_COLOR_COUNT |
|---|
| 30 | #define XFIG_USER_COLOR_LAST (XFIG_USER_COLOR_FIRST+XFIG_USER_COLOR_COUNT-1) |
|---|
| 31 | |
|---|
| 32 | // ------------------- |
|---|
| 33 | // Spoolable |
|---|
| 34 | |
|---|
| 35 | struct Spoolable { |
|---|
| 36 | virtual ~Spoolable() {} |
|---|
| 37 | |
|---|
| 38 | virtual const char *text() const = 0; |
|---|
| 39 | virtual const AW_rgb *rgb() const = 0; |
|---|
| 40 | }; |
|---|
| 41 | |
|---|
| 42 | class SpoolableString : public Spoolable { |
|---|
| 43 | string s; |
|---|
| 44 | public: |
|---|
| 45 | SpoolableString(const char *txt) : s(txt) {} |
|---|
| 46 | |
|---|
| 47 | const char *text() const OVERRIDE { return s.c_str(); } |
|---|
| 48 | const AW_rgb *rgb() const OVERRIDE { return NULp; } |
|---|
| 49 | }; |
|---|
| 50 | class SpoolableColor : public Spoolable { |
|---|
| 51 | AW_rgb color; |
|---|
| 52 | public: |
|---|
| 53 | SpoolableColor(const AW_rgb& color_) : color(color_) {} |
|---|
| 54 | |
|---|
| 55 | const char *text() const OVERRIDE { return NULp; } |
|---|
| 56 | const AW_rgb *rgb() const OVERRIDE { return &color; } |
|---|
| 57 | }; |
|---|
| 58 | |
|---|
| 59 | typedef SmartPtr<Spoolable> SpoolablePtr; |
|---|
| 60 | |
|---|
| 61 | // --------------------- |
|---|
| 62 | // color mapping |
|---|
| 63 | |
|---|
| 64 | static AW_rgb figStdColors[32] = { |
|---|
| 65 | // color names were taken from xfig color chooser. |
|---|
| 66 | // corresponding color values were taken from X11 source code: |
|---|
| 67 | // https://cgit.freedesktop.org/xorg/xserver/tree/os/oscolor.c |
|---|
| 68 | |
|---|
| 69 | 0x000000, // Black [index 0] |
|---|
| 70 | 0x0000ff, // Blue |
|---|
| 71 | 0x00ff00, // Green |
|---|
| 72 | 0x00ffff, // Cyan |
|---|
| 73 | 0xff0000, // Red |
|---|
| 74 | 0xff00ff, // Magenta |
|---|
| 75 | 0xffff00, // Yellow |
|---|
| 76 | 0xffffff, // White |
|---|
| 77 | 0x00008b, // Blue4 |
|---|
| 78 | 0x0000cd, // Blue3 |
|---|
| 79 | 0x0000ee, // Blue2 |
|---|
| 80 | 0xadd8e6, // LtBlue |
|---|
| 81 | 0x008b00, // Green4 |
|---|
| 82 | 0x00cd00, // Green3 |
|---|
| 83 | 0x00ee00, // Green2 |
|---|
| 84 | 0x008b8b, // Cyan4 |
|---|
| 85 | 0x00cdcd, // Cyan3 |
|---|
| 86 | 0x00eeee, // Cyan2 |
|---|
| 87 | 0x8b0000, // Red4 |
|---|
| 88 | 0xcd0000, // Red3 |
|---|
| 89 | 0xee0000, // Red2 |
|---|
| 90 | 0x8b008b, // Magenta4 |
|---|
| 91 | 0xcd00cd, // Magenta3 |
|---|
| 92 | 0xee00ee, // Magenta2 |
|---|
| 93 | 0x8b2323, // Brown4 |
|---|
| 94 | 0xcd3333, // Brown3 |
|---|
| 95 | 0xee3b3b, // Brown2 |
|---|
| 96 | 0x8b636c, // Pink4 |
|---|
| 97 | 0xcd919e, // Pink3 |
|---|
| 98 | 0xeea9b8, // Pink2 |
|---|
| 99 | 0xffc0cb, // Pink |
|---|
| 100 | 0xffd700, // Gold [index 31] |
|---|
| 101 | }; |
|---|
| 102 | |
|---|
| 103 | class UsedColor { |
|---|
| 104 | int count; // how often color occurred (=weight) |
|---|
| 105 | int index; // xfig color index (-2 = not assigned; -1 .. 31 = fixed xfig color; 32..543 = user defined color) |
|---|
| 106 | |
|---|
| 107 | static const int NOT_ASSIGNED = -2; |
|---|
| 108 | |
|---|
| 109 | public: |
|---|
| 110 | UsedColor() : count(0), index(NOT_ASSIGNED) {} |
|---|
| 111 | explicit UsedColor(int idx) : count(0), index(idx) {} // used to add figStdColors |
|---|
| 112 | |
|---|
| 113 | void inc() { ++count; } |
|---|
| 114 | void addCount(int toAdd) { count += toAdd; } |
|---|
| 115 | int getCount() const { return count; } |
|---|
| 116 | |
|---|
| 117 | bool isAssigned() const { return index != NOT_ASSIGNED; } |
|---|
| 118 | bool isStdColor() const { return index>=0 && index<XFIG_USER_COLOR_FIRST; } |
|---|
| 119 | |
|---|
| 120 | void assign(int idx) { |
|---|
| 121 | aw_assert(!isAssigned()); |
|---|
| 122 | aw_assert(idx>=XFIG_USER_COLOR_FIRST && idx<=XFIG_USER_COLOR_LAST); // only allow to assign user colors |
|---|
| 123 | index = idx; |
|---|
| 124 | aw_assert(isAssigned()); |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | int getIndex() const { aw_assert(isAssigned()); return index; } |
|---|
| 128 | }; |
|---|
| 129 | |
|---|
| 130 | inline double colorDistSquare(const AW_rgb& col1, const AW_rgb& col2) { |
|---|
| 131 | AW_rgb_diff diff = AW_rgb_normalized(AW_rgb16(col1)) - AW_rgb_normalized(AW_rgb16(col2)); |
|---|
| 132 | return diff.r()*diff.r() + diff.g()*diff.g() + diff.b()*diff.b(); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | class ColorMap { |
|---|
| 136 | // maps RGB values to xfig color indices |
|---|
| 137 | typedef map<AW_rgb,UsedColor> ColMap; |
|---|
| 138 | typedef map<AW_rgb,AW_rgb> Remap; // colors -> replaced by different color |
|---|
| 139 | |
|---|
| 140 | ColMap color; |
|---|
| 141 | Remap replaced; |
|---|
| 142 | |
|---|
| 143 | AW_rgb findCheapestReplaceableColor(AW_rgb *replaceBy) { |
|---|
| 144 | AW_rgb cheapest; |
|---|
| 145 | double penalty = numeric_limits<float>::max(); |
|---|
| 146 | |
|---|
| 147 | for (ColMap::const_iterator i = color.begin(); i != color.end(); ++i) { |
|---|
| 148 | const AW_rgb& col1 = i->first; |
|---|
| 149 | if (i->second.isStdColor()) continue; // do not attempt to replace standard colors |
|---|
| 150 | |
|---|
| 151 | const int amount = i->second.getCount(); |
|---|
| 152 | aw_assert(amount>0); // otherwise color should not appear in table |
|---|
| 153 | |
|---|
| 154 | for (ColMap::const_iterator j = color.begin(); j != color.end(); ++j) { |
|---|
| 155 | const AW_rgb& col2 = j->first; |
|---|
| 156 | |
|---|
| 157 | if (&col1 != &col2) { |
|---|
| 158 | double currPenalty = amount*colorDistSquare(col1, col2); |
|---|
| 159 | #if defined(DEBUG) && 0 |
|---|
| 160 | fprintf(stderr, "replace %s ", AW_rgb16(col1).ascii()); |
|---|
| 161 | fprintf(stderr, "by %s (amount=%i) -> penalty = %f\n", AW_rgb16(col2).ascii(), amount, currPenalty); |
|---|
| 162 | #endif |
|---|
| 163 | |
|---|
| 164 | if (currPenalty<penalty) { |
|---|
| 165 | penalty = currPenalty; |
|---|
| 166 | cheapest = col1; |
|---|
| 167 | *replaceBy = col2; |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | } |
|---|
| 171 | } |
|---|
| 172 | |
|---|
| 173 | aw_assert(penalty<numeric_limits<float>::max()); // no color found |
|---|
| 174 | aw_assert(penalty>0); // should not occur! |
|---|
| 175 | return cheapest; |
|---|
| 176 | } |
|---|
| 177 | |
|---|
| 178 | public: |
|---|
| 179 | ColorMap() { |
|---|
| 180 | // add fig standard colors to ColMap [=color indices 0..31] |
|---|
| 181 | for (int i = 0; i<XFIG_DEFAULT_COLOR_COUNT; ++i) { |
|---|
| 182 | color[figStdColors[i]] = UsedColor(i); |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | void add(AW_rgb rgb) { color[rgb].inc(); } |
|---|
| 187 | |
|---|
| 188 | bool operator()(const AW_rgb& c1, const AW_rgb& c2) const { |
|---|
| 189 | // operator to sort colors (often used colors first; strict order) |
|---|
| 190 | ColMap::const_iterator f1 = color.find(c1); |
|---|
| 191 | ColMap::const_iterator f2 = color.find(c2); |
|---|
| 192 | |
|---|
| 193 | aw_assert(f1 != color.end()); |
|---|
| 194 | aw_assert(f2 != color.end()); |
|---|
| 195 | |
|---|
| 196 | int cmp = f1->second.getCount() - f2->second.getCount(); |
|---|
| 197 | if (!cmp) cmp = c1-c2; // define strict order using rgb value |
|---|
| 198 | return cmp>0; |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | void mapColors(size_t allowed) { |
|---|
| 202 | size_t usedColors = color.size(); // with std-colors |
|---|
| 203 | |
|---|
| 204 | allowed += XFIG_DEFAULT_COLOR_COUNT; |
|---|
| 205 | |
|---|
| 206 | // reduce colors if too many colors in map: |
|---|
| 207 | while (usedColors>allowed) { |
|---|
| 208 | AW_rgb replaceBy; |
|---|
| 209 | AW_rgb cheapest = findCheapestReplaceableColor(&replaceBy); |
|---|
| 210 | |
|---|
| 211 | aw_assert(!color[cheapest].isStdColor()); |
|---|
| 212 | |
|---|
| 213 | // @@@ if amount of 'cheapest' and 'replaceBy' is (nearly) the same -> create mixed color and replace both? |
|---|
| 214 | |
|---|
| 215 | replaced[cheapest] = replaceBy; // store color replacement |
|---|
| 216 | color[replaceBy].addCount(color[cheapest].getCount()); // add occurrences of removed color to replacement color |
|---|
| 217 | |
|---|
| 218 | IF_ASSERTION_USED(size_t erased = )color.erase(cheapest); |
|---|
| 219 | aw_assert(erased == 1); |
|---|
| 220 | |
|---|
| 221 | --usedColors; |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | aw_assert(color.size() <= allowed); // failed to reduce colors |
|---|
| 225 | |
|---|
| 226 | vector<AW_rgb> sortedColors; |
|---|
| 227 | for (ColMap::iterator i = color.begin(); i != color.end(); ++i) { |
|---|
| 228 | sortedColors.push_back(i->first); |
|---|
| 229 | } |
|---|
| 230 | sort(sortedColors.begin(), sortedColors.end(), *this); |
|---|
| 231 | |
|---|
| 232 | // stupid mapping: |
|---|
| 233 | int nextIdx = XFIG_USER_COLOR_FIRST; |
|---|
| 234 | for (ColMap::iterator i = color.begin(); i != color.end(); ++i) { |
|---|
| 235 | UsedColor& used = i->second; |
|---|
| 236 | if (!used.isStdColor()) { |
|---|
| 237 | used.assign(nextIdx++); |
|---|
| 238 | } |
|---|
| 239 | } |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | void printUserColorDefinitions(FILE *xout) { |
|---|
| 243 | const AW_rgb *used[XFIG_USER_COLOR_LAST+1]; |
|---|
| 244 | |
|---|
| 245 | for (int i = 0; i<=XFIG_USER_COLOR_LAST; ++i) { |
|---|
| 246 | used[i] = NULp; |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | for (ColMap::const_iterator i = color.begin(); i != color.end(); ++i) { |
|---|
| 250 | #if defined(ASSERTION_USED) |
|---|
| 251 | const AW_rgb& rgb = i->first; |
|---|
| 252 | aw_assert(rgb != AW_NO_COLOR); |
|---|
| 253 | #endif |
|---|
| 254 | const UsedColor& ucol = i->second; |
|---|
| 255 | aw_assert(ucol.isAssigned()); |
|---|
| 256 | |
|---|
| 257 | int idx = ucol.getIndex(); |
|---|
| 258 | aw_assert(!used[idx]); |
|---|
| 259 | used[idx] = &(i->first); |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | // print custom color definitions sorted by index: |
|---|
| 263 | for (int i = XFIG_USER_COLOR_FIRST; i<=XFIG_USER_COLOR_LAST; ++i) { |
|---|
| 264 | if (used[i]) { |
|---|
| 265 | fprintf(xout, "0 %d #%06lx\n", i, *used[i]); |
|---|
| 266 | } |
|---|
| 267 | } |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | int rgb2index(AW_rgb rgb) const { |
|---|
| 271 | ColMap::const_iterator found = color.find(rgb); |
|---|
| 272 | while (found == color.end()) { |
|---|
| 273 | Remap::const_iterator remapped = replaced.find(rgb); |
|---|
| 274 | aw_assert(remapped != replaced.end()); |
|---|
| 275 | |
|---|
| 276 | rgb = remapped->second; |
|---|
| 277 | found = color.find(rgb); |
|---|
| 278 | } |
|---|
| 279 | return found->second.getIndex(); |
|---|
| 280 | } |
|---|
| 281 | }; |
|---|
| 282 | |
|---|
| 283 | // ----------------- |
|---|
| 284 | // SPOOLER |
|---|
| 285 | |
|---|
| 286 | class SPOOLER { |
|---|
| 287 | // SPOOLER delays output of xfig objects: |
|---|
| 288 | // 1. collect object code (while tracing information about used colors) |
|---|
| 289 | // 2. generate color table (probably reduced) |
|---|
| 290 | // 3. print object code (and remap colors) |
|---|
| 291 | |
|---|
| 292 | typedef list<SpoolablePtr> Spoolables; |
|---|
| 293 | |
|---|
| 294 | Spoolables objs; |
|---|
| 295 | |
|---|
| 296 | void initColors(ColorMap& colmap) { |
|---|
| 297 | for (Spoolables::const_iterator i = objs.begin(); i != objs.end(); ++i) { |
|---|
| 298 | const AW_rgb *rgb = (*i)->rgb(); |
|---|
| 299 | if (rgb) colmap.add(*rgb); |
|---|
| 300 | } |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | public: |
|---|
| 304 | SPOOLER() {} |
|---|
| 305 | |
|---|
| 306 | void put(const char *str) { objs.push_back(new SpoolableString(str)); } |
|---|
| 307 | void putColor(AW_rgb rgb) { objs.push_back(new SpoolableColor(rgb)); } |
|---|
| 308 | void putDefaultColor() { put("-1 "); } |
|---|
| 309 | |
|---|
| 310 | void spool(FILE *xout) { |
|---|
| 311 | ColorMap colmap; |
|---|
| 312 | |
|---|
| 313 | initColors(colmap); |
|---|
| 314 | colmap.mapColors(XFIG_USER_COLOR_COUNT); |
|---|
| 315 | colmap.printUserColorDefinitions(xout); |
|---|
| 316 | |
|---|
| 317 | while (!objs.empty()) { |
|---|
| 318 | SpoolablePtr next = objs.front(); |
|---|
| 319 | objs.pop_front(); |
|---|
| 320 | |
|---|
| 321 | const char *txt = next->text(); |
|---|
| 322 | if (txt) { |
|---|
| 323 | fputs(txt, xout); |
|---|
| 324 | } |
|---|
| 325 | else { |
|---|
| 326 | const AW_rgb *rgb = next->rgb(); |
|---|
| 327 | arb_assert(rgb); |
|---|
| 328 | |
|---|
| 329 | int idx = colmap.rgb2index(*rgb); |
|---|
| 330 | fprintf(xout, "%d ", idx); |
|---|
| 331 | } |
|---|
| 332 | } |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | }; |
|---|
| 336 | |
|---|
| 337 | // ------------------------- |
|---|
| 338 | // AW_device_print |
|---|
| 339 | |
|---|
| 340 | const double dpi_screen2printer = double(DPI_PRINTER)/DPI_SCREEN; |
|---|
| 341 | |
|---|
| 342 | inline double screen2printer(double val) { return val*dpi_screen2printer; } |
|---|
| 343 | inline int print_pos(AW_pos screen_pos) { return AW_INT(screen2printer(screen_pos)); } |
|---|
| 344 | |
|---|
| 345 | AW_DEVICE_TYPE AW_device_print::type() { return AW_DEVICE_PRINTER; } |
|---|
| 346 | |
|---|
| 347 | // Note: parameters to spoolf HAVE TO be wrapped in "()"! |
|---|
| 348 | #define spoolf(format) spooler->put(GBS_global_string format) |
|---|
| 349 | #define spools(str) spooler->put(str) |
|---|
| 350 | #define spoolColor(rgb) color_mode ? spooler->putColor(rgb) : spooler->putDefaultColor() |
|---|
| 351 | #define spoolDefaultColor() spooler->putDefaultColor() |
|---|
| 352 | |
|---|
| 353 | bool AW_device_print::line_impl(int gc, const LineVector& Line, AW_bitset filteri) { |
|---|
| 354 | bool drawflag = false; |
|---|
| 355 | if (filteri & filter) { |
|---|
| 356 | LineVector transLine = transform(Line); |
|---|
| 357 | LineVector clippedLine; |
|---|
| 358 | drawflag = clip(transLine, clippedLine); |
|---|
| 359 | |
|---|
| 360 | if (drawflag) { |
|---|
| 361 | const AW_GC *gcm = get_common()->map_gc(gc); |
|---|
| 362 | int line_width = gcm->get_line_width(); |
|---|
| 363 | |
|---|
| 364 | int line_mode = 0; |
|---|
| 365 | double gap_ratio = 0.0; |
|---|
| 366 | switch (gcm->get_line_style()) { |
|---|
| 367 | case AW_SOLID: /* use defaults from above*/ break; |
|---|
| 368 | case AW_DASHED: line_mode = 1; gap_ratio = 4.0; break; |
|---|
| 369 | case AW_DOTTED: line_mode = 2; gap_ratio = 2.0; break; |
|---|
| 370 | } |
|---|
| 371 | |
|---|
| 372 | aw_assert(xfig); // file has to be good! |
|---|
| 373 | |
|---|
| 374 | // type, subtype, style, thickness, pen_color, |
|---|
| 375 | // fill_color(new), depth, pen_style, area_fill, style_val, |
|---|
| 376 | // join_style(new), cap_style(new), radius, forward_arrow, |
|---|
| 377 | // backward_arrow, npoints |
|---|
| 378 | spoolf(("2 1 %d %d ", |
|---|
| 379 | line_mode, |
|---|
| 380 | AW_INT(line_width))); |
|---|
| 381 | spoolColor(gcm->get_last_fg_color()); |
|---|
| 382 | spoolf(("0 0 0 0 %5.3f 0 1 0 0 0 2\n\t%d %d %d %d\n", |
|---|
| 383 | gap_ratio, |
|---|
| 384 | print_pos(clippedLine.xpos()), |
|---|
| 385 | print_pos(clippedLine.ypos()), |
|---|
| 386 | print_pos(clippedLine.head().xpos()), |
|---|
| 387 | print_pos(clippedLine.head().ypos()))); |
|---|
| 388 | } |
|---|
| 389 | } |
|---|
| 390 | return drawflag; |
|---|
| 391 | } |
|---|
| 392 | |
|---|
| 393 | bool AW_device_print::invisible_impl(const AW::Position& pos, AW_bitset filteri) { |
|---|
| 394 | bool drawflag = false; |
|---|
| 395 | if (filteri & filter) { |
|---|
| 396 | Position trans = transform(pos); |
|---|
| 397 | |
|---|
| 398 | drawflag = !is_outside_clip(trans); |
|---|
| 399 | if (drawflag) { |
|---|
| 400 | spoolf(("2 1 0 1 7 7 50 -1 -1 0.000 0 0 -1 0 0 1\n\t%d %d\n", |
|---|
| 401 | print_pos(trans.xpos()), |
|---|
| 402 | print_pos(trans.ypos()))); |
|---|
| 403 | } |
|---|
| 404 | } |
|---|
| 405 | return drawflag; |
|---|
| 406 | } |
|---|
| 407 | |
|---|
| 408 | void AW_device_print::draw_text(int gc, const char *textBuffer, size_t textStart, size_t textLen, const AW::Position& pos) { |
|---|
| 409 | const AW_GC *gcm = get_common()->map_gc(gc); |
|---|
| 410 | |
|---|
| 411 | AW::Position POS(transform(pos)); |
|---|
| 412 | |
|---|
| 413 | char *pstr = strdup(textBuffer+textStart); // @@@ only copy textLen characters! |
|---|
| 414 | if (textLen < strlen(pstr)) pstr[textLen] = 0; // @@@ caller asserts textLen <= strlen(pstr) |
|---|
| 415 | else textLen = strlen(pstr); |
|---|
| 416 | |
|---|
| 417 | size_t i; |
|---|
| 418 | for (i=0; i<textLen; i++) { |
|---|
| 419 | if (pstr[i] < ' ') pstr[i] = '?'; |
|---|
| 420 | } |
|---|
| 421 | |
|---|
| 422 | int fontnr = AW_font_2_xfig(gcm->get_fontnr()); |
|---|
| 423 | if (fontnr<0) fontnr = - fontnr; |
|---|
| 424 | if (textBuffer[0]) { // @@@ incorrect or useless? |
|---|
| 425 | // 4=string 0=left color depth penstyle font font_size angle |
|---|
| 426 | // font_flags height length x y string |
|---|
| 427 | // (font/fontsize and color/depth have been switched from format 2.1 to 3.2) |
|---|
| 428 | |
|---|
| 429 | SizedCstr sstr(textBuffer); // @@@ shouldn't this use only part of textBuffer??? maybe 'pstr'? |
|---|
| 430 | |
|---|
| 431 | spools("4 0 "); |
|---|
| 432 | spoolColor(gcm->get_last_fg_color()); |
|---|
| 433 | spoolf(("0 0 %d %d 0.000 4 %d %d %d %d ", |
|---|
| 434 | fontnr, |
|---|
| 435 | gcm->get_fontsize(), |
|---|
| 436 | (int)gcm->get_font_limits().get_height(), |
|---|
| 437 | gcm->get_string_size(sstr), |
|---|
| 438 | print_pos(POS.xpos()), |
|---|
| 439 | print_pos(POS.ypos()))); |
|---|
| 440 | |
|---|
| 441 | spoolf(("%s\\001\n", pstr)); |
|---|
| 442 | } |
|---|
| 443 | free(pstr); |
|---|
| 444 | } |
|---|
| 445 | |
|---|
| 446 | GB_ERROR AW_device_print::open(const char *path) { |
|---|
| 447 | if (xfig) return "You cannot reopen a device"; |
|---|
| 448 | |
|---|
| 449 | xfig = fopen(path, "w"); |
|---|
| 450 | if (!xfig) return GB_IO_error("writing", path); |
|---|
| 451 | |
|---|
| 452 | fprintf(xfig, |
|---|
| 453 | "#FIG 3.2\n" // version |
|---|
| 454 | "Landscape\n" // "Portrait" |
|---|
| 455 | "Center\n" // "Flush Left" |
|---|
| 456 | "Metric\n" // "Inches" |
|---|
| 457 | "A4\n" // papersize |
|---|
| 458 | "100.0\n" // export&print magnification % |
|---|
| 459 | "Single\n" // Single/Multiple Pages |
|---|
| 460 | "-3\n" // background = transparent for gif export |
|---|
| 461 | "%i 2\n" // dpi, 2 = origin in upper left corner |
|---|
| 462 | , DPI_PRINTER); |
|---|
| 463 | |
|---|
| 464 | spooler = new SPOOLER; |
|---|
| 465 | |
|---|
| 466 | return NULp; |
|---|
| 467 | } |
|---|
| 468 | |
|---|
| 469 | int AW_common::find_data_color_idx(AW_rgb color) const { |
|---|
| 470 | for (int i=0; i<data_colors_size; i++) { |
|---|
| 471 | if (color == data_colors[i]) { |
|---|
| 472 | return i; |
|---|
| 473 | } |
|---|
| 474 | } |
|---|
| 475 | return -1; |
|---|
| 476 | } |
|---|
| 477 | |
|---|
| 478 | int AW_device_print::find_color_idx(AW_rgb color) { |
|---|
| 479 | int idx = -1; |
|---|
| 480 | if (color_mode) { |
|---|
| 481 | idx = get_common()->find_data_color_idx(color); |
|---|
| 482 | if (idx >= 0) idx += XFIG_USER_COLOR_FIRST; |
|---|
| 483 | } |
|---|
| 484 | return idx; |
|---|
| 485 | } |
|---|
| 486 | |
|---|
| 487 | void AW_device_print::close() { |
|---|
| 488 | arb_assert(correlated(spooler, xfig)); |
|---|
| 489 | |
|---|
| 490 | if (spooler) { |
|---|
| 491 | spooler->spool(xfig); |
|---|
| 492 | |
|---|
| 493 | delete spooler; |
|---|
| 494 | spooler = NULp; |
|---|
| 495 | |
|---|
| 496 | fclose(xfig); |
|---|
| 497 | xfig = NULp; |
|---|
| 498 | } |
|---|
| 499 | } |
|---|
| 500 | |
|---|
| 501 | 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*/) { |
|---|
| 502 | DOWNCAST(AW_device_print*, device)->draw_text(gc, textBuffer, textStart, textLen, pos); |
|---|
| 503 | return true; |
|---|
| 504 | } |
|---|
| 505 | bool AW_device_print::text_impl(int gc, const SizedCstr& cstr, const AW::Position& pos, AW_pos alignment, AW_bitset filteri) { |
|---|
| 506 | return text_overlay(gc, cstr, pos, alignment, filteri, AW_draw_string_on_printer); |
|---|
| 507 | } |
|---|
| 508 | |
|---|
| 509 | bool AW_device_print::box_impl(int gc, AW::FillStyle filled, const Rectangle& rect, AW_bitset filteri) { |
|---|
| 510 | bool drawflag = false; |
|---|
| 511 | if (filter & filteri) { |
|---|
| 512 | if (filled.somehow()) { |
|---|
| 513 | Position q[4]; |
|---|
| 514 | q[0] = rect.upper_left_corner(); |
|---|
| 515 | q[1] = rect.upper_right_corner(); |
|---|
| 516 | q[2] = rect.lower_right_corner(); |
|---|
| 517 | q[3] = rect.lower_left_corner(); |
|---|
| 518 | |
|---|
| 519 | drawflag = polygon(gc, filled, 4, q, filteri); |
|---|
| 520 | } |
|---|
| 521 | else { |
|---|
| 522 | drawflag = generic_box(gc, rect, filteri); |
|---|
| 523 | } |
|---|
| 524 | } |
|---|
| 525 | return drawflag; |
|---|
| 526 | } |
|---|
| 527 | |
|---|
| 528 | AW_device::Fill_Style AW_device_print::detectFillstyleForGreylevel(int gc, AW::FillStyle filled) { // @@@ directly pass greylevel (or AW_GC?) |
|---|
| 529 | // @@@ DRY code here vs setFillstyleForGreylevel |
|---|
| 530 | |
|---|
| 531 | switch (filled.get_style()) { |
|---|
| 532 | case AW::FillStyle::SOLID: return FS_SOLID; |
|---|
| 533 | case AW::FillStyle::EMPTY: return FS_EMPTY; |
|---|
| 534 | |
|---|
| 535 | case AW::FillStyle::SHADED: |
|---|
| 536 | case AW::FillStyle::SHADED_WITH_BORDER: |
|---|
| 537 | break; // detect using greylevel |
|---|
| 538 | } |
|---|
| 539 | |
|---|
| 540 | AW_grey_level greylevel = get_grey_level(gc); |
|---|
| 541 | |
|---|
| 542 | static const float PART = 1.0/22; // exact for color_mode==false |
|---|
| 543 | |
|---|
| 544 | if (greylevel<PART) return FS_EMPTY; |
|---|
| 545 | if (greylevel>(1.0-PART)) return FS_SOLID; |
|---|
| 546 | |
|---|
| 547 | return FS_GREY; |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | int AW_device_print::calcAreaFill(AW_device::Fill_Style fillStyle, AW_grey_level grey_level) { |
|---|
| 551 | int area_fill = -1; |
|---|
| 552 | if (fillStyle == FS_SOLID) { // @@@ use switch here |
|---|
| 553 | area_fill = 20; |
|---|
| 554 | } |
|---|
| 555 | else if (fillStyle != FS_EMPTY) { |
|---|
| 556 | aw_assert(grey_level>=0.0 && grey_level<=1.0); |
|---|
| 557 | if (color_mode) { |
|---|
| 558 | area_fill = AW_INT(40-20*grey_level); // 20 = full saturation; 40 = white |
|---|
| 559 | aw_assert(area_fill != 40); // @@@ should better use fillStyle==FS_EMPTY in that case |
|---|
| 560 | } |
|---|
| 561 | else { |
|---|
| 562 | area_fill = AW_INT(20*grey_level); // 1=light 19=dark 20=solid |
|---|
| 563 | aw_assert(area_fill != 0); // @@@ should better use fillStyle==FS_EMPTY in that case |
|---|
| 564 | } |
|---|
| 565 | aw_assert(area_fill != 20); // @@@ should better use fillStyle==FS_SOLID in that case |
|---|
| 566 | } |
|---|
| 567 | return area_fill; |
|---|
| 568 | } |
|---|
| 569 | |
|---|
| 570 | bool AW_device_print::circle_impl(int gc, AW::FillStyle filled, const Position& center, const AW::Vector& radius, AW_bitset filteri) { |
|---|
| 571 | bool drawflag = false; |
|---|
| 572 | if (filteri & filter) { |
|---|
| 573 | aw_assert(radius.x()>0 && radius.y()>0); |
|---|
| 574 | Rectangle Box(center-radius, center+radius); |
|---|
| 575 | Rectangle screen_box = transform(Box); |
|---|
| 576 | Rectangle clipped_box; |
|---|
| 577 | drawflag = box_clip(screen_box, clipped_box); |
|---|
| 578 | bool half_visible = (clipped_box.surface()*2) > screen_box.surface(); |
|---|
| 579 | |
|---|
| 580 | drawflag = drawflag && half_visible; |
|---|
| 581 | // @@@ correct behavior would be to draw an arc if only partly visible |
|---|
| 582 | |
|---|
| 583 | if (drawflag) { |
|---|
| 584 | const AW_GC *gcm = get_common()->map_gc(gc); |
|---|
| 585 | |
|---|
| 586 | // force into clipped_box (hack): |
|---|
| 587 | Position Center = clipped_box.centroid(); |
|---|
| 588 | Vector screen_radius = clipped_box.diagonal()/2; |
|---|
| 589 | |
|---|
| 590 | int cx = print_pos(Center.xpos()); |
|---|
| 591 | int cy = print_pos(Center.ypos()); |
|---|
| 592 | int rx = print_pos(screen_radius.x()); |
|---|
| 593 | int ry = print_pos(screen_radius.y()); |
|---|
| 594 | |
|---|
| 595 | { |
|---|
| 596 | int subtype = (rx == ry) ? 3 : 1; // 3(circle) 1(ellipse) |
|---|
| 597 | subtype = 3; // @@@ remove after refactoring |
|---|
| 598 | spoolf(("1 %d ", subtype)); // type + subtype: |
|---|
| 599 | } |
|---|
| 600 | |
|---|
| 601 | { |
|---|
| 602 | int line_width = gcm->get_line_width(); |
|---|
| 603 | spoolf(("%d %d ", AW_SOLID, line_width)); // line_style + line_width |
|---|
| 604 | |
|---|
| 605 | { |
|---|
| 606 | AW_rgb color = gcm->get_last_fg_color(); |
|---|
| 607 | Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled); |
|---|
| 608 | int area_fill = calcAreaFill(fillStyle, gcm->get_grey_level()); |
|---|
| 609 | |
|---|
| 610 | spoolColor(color); // pen_color |
|---|
| 611 | if (area_fill == -1) { |
|---|
| 612 | spoolDefaultColor(); // fill color |
|---|
| 613 | } |
|---|
| 614 | else { |
|---|
| 615 | spoolColor(color); // fill color |
|---|
| 616 | } |
|---|
| 617 | spoolf(("0 0 %d ", area_fill)); // depth + pen_style + area_fill |
|---|
| 618 | } |
|---|
| 619 | spoolf(("0.000 1 0.0000 ")); // style_val + direction + angle (x-axis) |
|---|
| 620 | } |
|---|
| 621 | |
|---|
| 622 | spoolf(("%d %d ", cx, cy)); // center |
|---|
| 623 | spoolf(("%d %d ", rx, ry)); // radius |
|---|
| 624 | spoolf(("%d %d ", cx, cy)); // start |
|---|
| 625 | spoolf(("%d %d\n", print_pos(Center.xpos()+screen_radius.x()), cy)); // end |
|---|
| 626 | } |
|---|
| 627 | } |
|---|
| 628 | return drawflag; |
|---|
| 629 | } |
|---|
| 630 | |
|---|
| 631 | 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) { |
|---|
| 632 | bool drawflag = false; |
|---|
| 633 | if (filteri && filter) { |
|---|
| 634 | aw_assert(radius.x()>0 && radius.y()>0); |
|---|
| 635 | Rectangle Box(center-radius, center+radius); |
|---|
| 636 | Rectangle screen_box = transform(Box); |
|---|
| 637 | Rectangle clipped_box; |
|---|
| 638 | drawflag = box_clip(screen_box, clipped_box); |
|---|
| 639 | bool half_visible = (clipped_box.surface()*2) > screen_box.surface(); |
|---|
| 640 | |
|---|
| 641 | drawflag = drawflag && half_visible; |
|---|
| 642 | // @@@ correct behavior would be to draw an arc if only partly visible |
|---|
| 643 | |
|---|
| 644 | UNCOVERED(); // @@@ AW_device_print::arc_impl not triggered from test code. |
|---|
| 645 | // Uses via method arc(): |
|---|
| 646 | // - from SECEDIT to draw '~'-bonds (may be saved to xfig) |
|---|
| 647 | // - from EDIT4 to mark protein codon triples (NEVER saved to xfig) |
|---|
| 648 | // AW_device_Xm version is used indirectly via method circle_impl(). |
|---|
| 649 | |
|---|
| 650 | if (drawflag) { |
|---|
| 651 | const AW_GC *gcm = get_common()->map_gc(gc); |
|---|
| 652 | |
|---|
| 653 | // force into clipped_box (hack): |
|---|
| 654 | Position Center = clipped_box.centroid(); |
|---|
| 655 | Vector screen_radius = clipped_box.diagonal()/2; |
|---|
| 656 | |
|---|
| 657 | int cx = print_pos(Center.xpos()); |
|---|
| 658 | int cy = print_pos(Center.ypos()); |
|---|
| 659 | int rx = print_pos(screen_radius.x()); |
|---|
| 660 | int ry = print_pos(screen_radius.y()); |
|---|
| 661 | |
|---|
| 662 | bool use_spline = (rx != ry); // draw interpolated spline for ellipsoid arcs |
|---|
| 663 | |
|---|
| 664 | spools(use_spline ? "3 4 " : "5 1 "); // type + subtype: |
|---|
| 665 | |
|---|
| 666 | { |
|---|
| 667 | int line_width = gcm->get_line_width(); |
|---|
| 668 | spoolf(("%d %d ", AW_SOLID, line_width)); // line_style + line_width |
|---|
| 669 | |
|---|
| 670 | { |
|---|
| 671 | AW_rgb color = gcm->get_last_fg_color(); |
|---|
| 672 | Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled); |
|---|
| 673 | int area_fill = calcAreaFill(fillStyle, gcm->get_grey_level()); |
|---|
| 674 | |
|---|
| 675 | spoolColor(color); // pen_color |
|---|
| 676 | if (area_fill == -1) { |
|---|
| 677 | spoolDefaultColor(); // fill color |
|---|
| 678 | } |
|---|
| 679 | else { |
|---|
| 680 | spoolColor(color); // fill color |
|---|
| 681 | } |
|---|
| 682 | spoolf(("0 0 %d ", area_fill)); // depth + pen_style + area_fill |
|---|
| 683 | #if defined(UNIT_TESTS) |
|---|
| 684 | if (area_fill != -1) { UNCOVERED(); } // @@@ filled arcs not tested |
|---|
| 685 | else { UNCOVERED(); } // @@@ unfilled arcs not tested |
|---|
| 686 | #endif |
|---|
| 687 | } |
|---|
| 688 | spools("0.000 1 "); // style_val + cap_style |
|---|
| 689 | if (!use_spline) spools("1 "); // direction |
|---|
| 690 | spools("0 0 "); // 2 arrows |
|---|
| 691 | } |
|---|
| 692 | |
|---|
| 693 | Angle a0(Angle::deg2rad*start_degrees); |
|---|
| 694 | Angle a1(Angle::deg2rad*(start_degrees+arc_degrees)); |
|---|
| 695 | |
|---|
| 696 | if (use_spline) { |
|---|
| 697 | const int MAX_ANGLE_STEP = 45; // degrees |
|---|
| 698 | |
|---|
| 699 | int steps = (abs(arc_degrees)-1)/MAX_ANGLE_STEP+1; |
|---|
| 700 | Angle step(Angle::deg2rad*double(arc_degrees)/steps); |
|---|
| 701 | |
|---|
| 702 | spoolf(("%d\n\t", steps+1)); // npoints |
|---|
| 703 | |
|---|
| 704 | double rmax, x_factor, y_factor; |
|---|
| 705 | if (rx>ry) { |
|---|
| 706 | rmax = rx; |
|---|
| 707 | x_factor = 1.0; |
|---|
| 708 | y_factor = double(ry)/rx; |
|---|
| 709 | } |
|---|
| 710 | else { |
|---|
| 711 | rmax = ry; |
|---|
| 712 | x_factor = double(rx)/ry; |
|---|
| 713 | y_factor = 1.0; |
|---|
| 714 | } |
|---|
| 715 | |
|---|
| 716 | for (int n = 0; n <= steps; ++n) { |
|---|
| 717 | Vector toCircle = a0.normal()*rmax; |
|---|
| 718 | Vector toEllipse(toCircle.x()*x_factor, toCircle.y()*y_factor); |
|---|
| 719 | Position onEllipse = Center+toEllipse; |
|---|
| 720 | |
|---|
| 721 | int x = print_pos(onEllipse.xpos()); |
|---|
| 722 | int y = print_pos(onEllipse.ypos()); |
|---|
| 723 | |
|---|
| 724 | spoolf((" %d %d", x, y)); |
|---|
| 725 | |
|---|
| 726 | if (n<steps) { |
|---|
| 727 | if (n == steps-1) a0 = a1; |
|---|
| 728 | else a0 += step; |
|---|
| 729 | } |
|---|
| 730 | } |
|---|
| 731 | spools("\n\t"); |
|---|
| 732 | for (int n = 0; n <= steps; ++n) { |
|---|
| 733 | // -1 = interpolate; 0 = discontinuity; 1 = approximate |
|---|
| 734 | spoolf((" %d", (n == 0 || n == steps) ? 0 : -1)); |
|---|
| 735 | } |
|---|
| 736 | spools("\n"); |
|---|
| 737 | } |
|---|
| 738 | else { |
|---|
| 739 | spoolf(("%d %d ", cx, cy)); // center |
|---|
| 740 | |
|---|
| 741 | Angle am(Angle::deg2rad*(start_degrees+arc_degrees*0.5)); |
|---|
| 742 | |
|---|
| 743 | double r = screen_radius.x(); |
|---|
| 744 | Position p0 = Center+a0.normal()*r; |
|---|
| 745 | Position pm = Center+am.normal()*r; |
|---|
| 746 | Position p1 = Center+a1.normal()*r; |
|---|
| 747 | |
|---|
| 748 | spoolf(("%d %d ", print_pos(p0.xpos()), print_pos(p0.ypos()))); |
|---|
| 749 | spoolf(("%d %d ", print_pos(pm.xpos()), print_pos(pm.ypos()))); |
|---|
| 750 | spoolf(("%d %d\n", print_pos(p1.xpos()), print_pos(p1.ypos()))); |
|---|
| 751 | } |
|---|
| 752 | } |
|---|
| 753 | } |
|---|
| 754 | return drawflag; |
|---|
| 755 | } |
|---|
| 756 | |
|---|
| 757 | bool AW_device_print::polygon_impl(int gc, AW::FillStyle filled, int npos, const Position *pos, AW_bitset filteri) { |
|---|
| 758 | bool drawflag = false; |
|---|
| 759 | if (filter & filteri) { |
|---|
| 760 | drawflag = generic_polygon(gc, npos, pos, filteri); |
|---|
| 761 | if (drawflag) { // line visible -> area fill needed |
|---|
| 762 | const AW_GC *gcm = get_common()->map_gc(gc); |
|---|
| 763 | |
|---|
| 764 | int line_width = gcm->get_line_width(); |
|---|
| 765 | |
|---|
| 766 | spools("2 3 "); |
|---|
| 767 | spoolf(("%d %d ", AW_SOLID, line_width)); |
|---|
| 768 | |
|---|
| 769 | { |
|---|
| 770 | AW_rgb color = gcm->get_last_fg_color(); |
|---|
| 771 | Fill_Style fillStyle = detectFillstyleForGreylevel(gc, filled); |
|---|
| 772 | int area_fill = calcAreaFill(fillStyle, gcm->get_grey_level()); |
|---|
| 773 | |
|---|
| 774 | spoolColor(color); // pen_color |
|---|
| 775 | if (area_fill == -1) { |
|---|
| 776 | spoolDefaultColor(); // fill color |
|---|
| 777 | } |
|---|
| 778 | else { |
|---|
| 779 | spoolColor(color); // fill color |
|---|
| 780 | } |
|---|
| 781 | spoolf(("0 0 %d ", area_fill)); // depth + pen_style + area_fill |
|---|
| 782 | } |
|---|
| 783 | |
|---|
| 784 | spools("0.000 0 0 -1 0 0 "); |
|---|
| 785 | |
|---|
| 786 | spoolf(("%d\n", npos+1)); |
|---|
| 787 | |
|---|
| 788 | // @@@ method used here for clipping leads to wrong results, |
|---|
| 789 | // since group border (drawn by generic_polygon() above) is clipped correctly, |
|---|
| 790 | // but filled content is clipped different. |
|---|
| 791 | // |
|---|
| 792 | // fix: clip the whole polygon before drawing border |
|---|
| 793 | |
|---|
| 794 | for (int i=0; i <= npos; i++) { |
|---|
| 795 | int j = i == npos ? 0 : i; // print pos[0] after pos[n-1] |
|---|
| 796 | |
|---|
| 797 | Position transPos = transform(pos[j]); |
|---|
| 798 | Position clippedPos; |
|---|
| 799 | ASSERT_RESULT(bool, true, force_into_clipbox(transPos, clippedPos)); |
|---|
| 800 | spoolf((" %d %d\n", print_pos(clippedPos.xpos()), print_pos(clippedPos.ypos()))); |
|---|
| 801 | } |
|---|
| 802 | } |
|---|
| 803 | } |
|---|
| 804 | return drawflag; |
|---|
| 805 | } |
|---|