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 = AW_NO_COLOR; |
---|
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 | } |
---|