/**
* The Paper Tape Project -- Visualisation sub project
* lochstreifen.c - Painting Paper Tapes according to ECMA-10, with cairographics.
* $Id$
*
* This is a rewrite from the famous Cairo paper tape drawing routines. This
* c file implements a (simple) object called "LOCHSTREIFEN".
*
*
*
*
*
* (c) Copyright 2007, 2008 Sven Köppel
*
* This program is free software; you can redistribute
* it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, see
* http://www.gnu.org/licenses/
*
**/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lochstreifen.h"
/**
* Create a new LOCHSTREIFEN object and return it. This is a quite light
* object system implementation, as you get a reference to the object.
* You are not supposed to manipulate the elements of the LOCHSTREIFEN
* structure directly.
*
* Btw, the german word "LOCHSTREIFEN" only means "PAPER TAPE".
*
**/
LOCHSTREIFEN *lochstreifen_new() {
LOCHSTREIFEN *l = malloc (sizeof(LOCHSTREIFEN));
// set default values:
l->data_length = 0;
l->data = NULL;
l->highlight_region_start = 0;
l->highlight_region_end = 0;
l->highlight_region_color = NULL;
l->highlight_row_number = 0;
l->highlight_row_color = NULL;
l->highlight_bit_row = 0;
l->highlight_bit_track = 0;
l->highlight_bit_color = NULL;
l->clip = NULL;
l->outer_background_color = LOCHSTREIFEN_DEFAULT_OUTER_BACKGROUND_COLOR;
l->papertape_background_color = LOCHSTREIFEN_DEFAULT_PAPERTAPE_BACKGROUND_COLOR;
l->one_code_hole_color = LOCHSTREIFEN_DEFAULT_ONE_CODE_HOLE_COLOR;
l->zero_code_hole_color = LOCHSTREIFEN_DEFAULT_ZERO_CODE_HOLE_COLOR;
l->feed_hole_color = LOCHSTREIFEN_DEFAULT_FEED_HOLE_COLOR;
cairo_matrix_init(&l->matrix, 1, 0, 0, 1, 0, 0); // default = 1:1 original
l->matrix_inverse = l->matrix;
l->row_callback = NULL;
l->row_callback_user_data = NULL;
return l;
}
/**
* Copy everything from the LOCHSTREIFEN template, except the data. They
* won't be copied, just set, because LOCHSTREIFEN doesn't manage your
* data. If you want to copy them, call something like
* LOCHSTREIFEN* copy = lochstreifen_copy(old);
* byte_t* new_data = malloc(old->data_length);
* memcpy(new_data, old->data, old->data_length);
* lochstreifen_set_data(copy, old->data_length, new_data);
**/
LOCHSTREIFEN* lochstreifen_copy(const LOCHSTREIFEN* template) {
LOCHSTREIFEN *l = malloc (sizeof(LOCHSTREIFEN));
#define COPY_RGBA_PATTERN(name) { double r,g,b,a; \
if(!cairo_pattern_get_rgba(template->name, &r, &g, &b, &a)) \
l->name = cairo_pattern_create_rgba(r,g,b,a); }
// make it quite verbose ;-)
l->data_length = template->data_length;
l->data = template->data;
l->highlight_region_start = template->highlight_region_start;
l->highlight_region_end = template->highlight_region_end;
COPY_RGBA_PATTERN(highlight_region_color);
l->highlight_row_number = template->highlight_row_number;
COPY_RGBA_PATTERN(highlight_row_color);
l->highlight_bit_row = template->highlight_bit_row;
l->highlight_bit_track = template->highlight_bit_track;
COPY_RGBA_PATTERN(highlight_bit_color);
l->clip = malloc(sizeof(cairo_rectangle_t));
memcpy(l->clip, template->clip, sizeof(cairo_rectangle_t));
COPY_RGBA_PATTERN(outer_background_color);
COPY_RGBA_PATTERN(papertape_background_color);
COPY_RGBA_PATTERN(one_code_hole_color);
COPY_RGBA_PATTERN(zero_code_hole_color);
COPY_RGBA_PATTERN(feed_hole_color);
l->matrix = template->matrix;
l->matrix_inverse = template->matrix_inverse;
l->row_callback = template->row_callback;
l->row_callback_user_data = template->row_callback_user_data;
return l;
}
/**
* Delete every mallocated structure in the LOCHSTREIFEN (colors, etc.),
* *except* from the data. You have to manage them on your own.
* In the end, it will free the LOCHSTREIFEN itself.
* This won't touch your row_callback_user_data.
* @return Nothing, because there's nothing left. Except your data, okay.
**/
void lochstreifen_free(LOCHSTREIFEN* l) {
if(l->highlight_region_color != NULL) free(l->highlight_region_color);
if(l->highlight_row_color != NULL) free(l->highlight_row_color);
if(l->highlight_bit_color != NULL) free(l->highlight_bit_color);
if(l->clip != NULL) free(l->clip);
if(l->outer_background_color != NULL) free(l->outer_background_color);
if(l->papertape_background_color != NULL) free(l->papertape_background_color);
if(l->one_code_hole_color != NULL) free(l->one_code_hole_color);
if(l->zero_code_hole_color != NULL) free(l->zero_code_hole_color);
if(l->feed_hole_color != NULL) free(l->feed_hole_color);
}
/**
* A small debug function that will print out all fields of the LOCHSTREIFEN
* structure well ordered to stderr.
*
**/
void lochstreifen_print_debug(LOCHSTREIFEN* l) {
int x;
#define COLORP(color) if(color == NULL) fprintf(stderr, " color ist NULL.\n"); \
else if(cairo_pattern_get_type(color) == CAIRO_PATTERN_TYPE_SOLID) {\
double r,g,b,a; cairo_pattern_get_rgba(color, &r, &g, &b, &a); \
fprintf(stderr, " SOLID: R=%f, G=%f, B=%f, A=%f\n", r, g, b, a); \
} else fprintf(stderr, " NO SOLID PATTERN (special pattern)\n");
#define COLORP_VERBOSE(color1, name) \
fprintf(stderr, " * "name": %s\n", (color1) != NULL ? "ENABLED" : "disabled");\
COLORP(color1);
if(l != NULL)
fprintf(stderr, "Debugging papertape at %p:\n", l);
else {
fprintf(stderr, "Cannot debug: Papertape is NULL!\n");
return;
}
// 1. Point: Print out data.
fprintf(stderr, "1# DATA AND OUTPUT IMAGE SIZE INFORMATION\n");
fprintf(stderr, " * Data: length=%i bytes", l->data_length);
if(l->data_length > 0 && l->data != NULL) {
// there are data
fprintf(stderr, ":\n { ");
for(x=0;;) {
fprintf(stderr, "0x%x", l->data[x]);
if(++x == l->data_length) {
fprintf(stderr, " };\n");
break;
} else if(x == 10) { // magic number: print first 10 bytes.
fprintf(stderr, ", ... };\n");
break;
} else
fprintf(stderr, ", ");
}
} else if(l->data == NULL) {
fprintf(stderr, " BUT data are NULL!\n");
} else // data_length == 0.
fprintf(stderr, " (empty data)\n");
fprintf(stderr, " * Unscaled dimensions:\n");
fprintf(stderr, " width of image (length of papertape) = %f\n", lochstreifen_get_length(l));
fprintf(stderr, " height of image (width of papertape) = 1\n");
// 2. Point: Print out colors
fprintf(stderr, "\n2# ELEMENT COLORS\n");
COLORP_VERBOSE(l->outer_background_color, "outer background");
COLORP_VERBOSE(l->papertape_background_color, "papertape background");
COLORP_VERBOSE(l->one_code_hole_color, "one code holes");
COLORP_VERBOSE(l->zero_code_hole_color, "zero code holes");
COLORP_VERBOSE(l->feed_hole_color, "feed holes");
// 3. Point: Print out highlighted thingies
fprintf(stderr, "\n3# HIGHLIGHT COLORS\n");
fprintf(stderr, l->highlight_region_color != NULL ? " * Highlight region: start=%i end=%i\n"
: " * Highlight region: disabled\n", l->highlight_region_start, l->highlight_region_end);
COLORP(l->highlight_region_color);
fprintf(stderr, l->highlight_row_color != NULL ? " * Highlight row: number=%i\n"
: " * Highlight row: disabled\n", l->highlight_row_number);
COLORP(l->highlight_row_color);
fprintf(stderr, l->highlight_bit_color != NULL ? " * Higlight bit: row=%i track=%i\n"
: " * Highlight bit: disabled\n", l->highlight_bit_row, l->highlight_bit_track);
COLORP(l->highlight_bit_color);
// 4. Point: Print out information about clippings
fprintf(stderr, "\n4# SPECIAL STRUCTURES\n");
if(l->clip == NULL)
fprintf(stderr, " * Clipping: disabled\n");
else {
fprintf(stderr, " * Clipping: ENABLED\n");
fprintf(stderr, " x=%f, y=%f, width=%f, height=%f\n",
l->clip->x, l->clip->y, l->clip->width, l->clip->height);
}
// 5. Point: Affine Matrix
fprintf(stderr, " * Current transformation matrix:\n");
fprintf(stderr, " x_new = [xx=%f]*x + [xy=%f]*y + [x0=%f]\n",
l->matrix.xx, l->matrix.xy, l->matrix.x0);
fprintf(stderr, " y_new = [yx=%f]*x + [yy=%f]*y + [y0=%f]\n",
l->matrix.yx, l->matrix.yy, l->matrix.y0);
fprintf(stderr, " source dimensions: x_max = %f x y_max = %f\n",
LOCHSTREIFEN_LENGTH, LOCHSTREIFEN_WIDTH);
fprintf(stderr, " target dimensions: width = %i x height = %i\n",
lochstreifen_get_target_width(l),
lochstreifen_get_target_height(l));
// 6. Point: Debugging
fprintf(stderr, " * Debugging: %s\n", l->debug ? "ENABLED" : "disabled");
fprintf(stderr, "END OF LOCHSTREIFEN\n");
}
/**
* Set the data which LOCHSTREIFEN is supposed to punch out. You shall give
* the LOCSTREIFEN object only a copy of your data, never give it a sensible
* data array. The LOCHSTREIFEN object won't write into your data, but afterwards
* it will free them. So it treats the data as it's own.
*
**/
void lochstreifen_set_data(LOCHSTREIFEN *l, int data_length, byte_t *data) {
l->data_length = data_length;
//if(l->data != NULL)
// free(l->data);
l->data = data;
}
/**
* Adds Null Bytes (0x00) at start and/or end of the data array. This dynamically
* increases the data array.
*
*
**/
void lochstreifen_add_null_bytes(LOCHSTREIFEN *l, int start, int end) {
byte_t *new_data;
// only to be sure realloc won't decrease the size of the array:
if(start < 0) start = 0;
if(end < 0) end = 0;
// make array bigger to contain new start/end chunks
new_data = realloc(l->data, l->data_length + start + end);
// check wheter realloc exited successfully. We don't want segfaults
if(new_data == NULL)
return;
// move data to the middle
memmove(new_data + start, new_data, l->data_length);
// fill new start chunk with zero bytes
memset(new_data, 0x00, start);
// fill new end chunk with zero bytes
memset(new_data + start + l->data_length, 0x00, end);
// that's it.
// fix the data link. Don't free the old data. Don't know exactly
// how realloc works, sorry ;-)
l->data = new_data;
}
/**
* Get the length of a LOCHSTREIFEN. It depends on the length of the data array
* and the papertape dimension constants.
*
*
**/
double lochstreifen_get_length(LOCHSTREIFEN *l) {
return LOCHSTREIFEN_LENGTH;
}
/**
* Tell LOCHSTREIFEN to highlight a region of rows. Start counts from 0 and is already
* in the selection, end is no more in selection. Example with start = 2; end = 5:
*
* 0 1 2 3 4 5 6 <- rows
* |_________________|
* highlight region
**/
void lochstreifen_set_highlight_region(LOCHSTREIFEN *l, row_t start, row_t end) {
l->highlight_region_start = start;
l->highlight_region_end = end;
if(l->highlight_region_color == NULL)
l->highlight_row_color = LOCHSTREIFEN_DEFAULT_HIGHLIGHT_REGION_COLOR;
}
/**
* Get the selection bounds of the highlighted rows. Give Links to your variables
* where the function stores the start/end integers.
* @return TRUE if there is an highlight selection, FALSE otherwiese
**/
int lochstreifen_get_highlight_region(LOCHSTREIFEN *l, row_t *start, row_t *end) {
if(l->highlight_region_color == NULL || l->highlight_region_start == LOCHSTREIFEN_NO_ROW)
return 0;
if(start != NULL)
*start = l->highlight_region_start;
if(end != NULL)
*end = l->highlight_region_end;
return 1;
}
/**
* Remove the highlighted region (that is, make it no more highlighted).
**/
void lochstreifen_remove_highlight_region(LOCHSTREIFEN *l) {
//l->highlight_region_color = NULL;
// was stupid, this is more intelligent:
l->highlight_region_start = LOCHSTREIFEN_NO_ROW;
}
/**
* Tell LOCHSTREIFEN to highlight one, only one special row. Starts counting from 0.
* Example with start = 3:
*
* 0 1 2 3 4 5
* |_____|
* highlighted row
*
* Principally this is the same feature like lochstreifen_set_highlight_region, having
* (in the above example) start = 3 and end = 4. You can just use another color and
* use both techniques concurrently, whereby the highlighted row will ALWAYS be ABOVE
* the highlighted chunk of rows. An application example is e.g.: The highlighted region
* of rows are selected rows, the highlighted special row is the cursor position row.
* This would be quite usable with an implementation of the GtkEditable interface, like
* GtkPaperTape.
**/
void lochstreifen_set_highlight_row(LOCHSTREIFEN *l, row_t number_of_row) {
l->highlight_row_number = number_of_row;
if(l->highlight_row_color == NULL)
l->highlight_row_color = LOCHSTREIFEN_DEFAULT_HIGHLIGHT_ROW_COLOR;
}
/**
* Get the highlighted special row. If there is no highlighted row, it will return
* LOCHSTREIFEN_NO_ROW.
* @return the number of the special row, counting from zero.
**/
int lochstreifen_get_highlight_row(LOCHSTREIFEN *l) {
return l->highlight_row_number;
}
/**
* Remove the highlight at the special selected row.
**/
void lochstreifen_remove_highlight_row(LOCHSTREIFEN *l) {
//l->highlight_row_color = NULL; /// nein, das ist dumm, weil es den highlight komplett entfernen würde.
l->highlight_row_number = LOCHSTREIFEN_NO_ROW; // das ist schlau.
}
/**
* Tell LOCHSTREIFEN to highlight one special bit on the complete papertape. To identify
* this bit, you give the row and track number (byte id and bit id) for this bit. E.g.
* with row = 3, bit = 5 you'll get something like:
*
* ------- DIRECTION OF MOVEMENT ------->
* track 8 8 8 8 8 8
* track 7 7 7 7 7 7
* track 6 6 6 +-----+ 6 6
* track 5 5 5 | 5 | 5 5 (of course the highlight won't affect any
* track 4 4 4 +-----+ 4 4 other bit, row 3, track 6 and track 4
* feed - - - - - - are not visible due to ASCII limitations
* track 3 3 3 3 3 3 ;-) )
* track 2 2 2 2 2 2
* track 1 1 1 1 1 1
*
* row: 0 1 2 3 4 5
* ^-- this row
*
* The bit highlight will appear on top of the row highlight and region highlight. You
* can combine the bit highlight with row highlight (e.g. in the example above you can
* highlight row 3, too) and even with the region highlight (e.g. having a region from
* row 2 to row 5).
**/
void lochstreifen_set_highlight_bit(LOCHSTREIFEN *l, row_t number_of_row, track_t number_of_bit) {
l->highlight_bit_row = number_of_row;
l->highlight_bit_track = number_of_bit;
if(l->highlight_bit_color == NULL)
l->highlight_bit_color = LOCHSTREIFEN_DEFAULT_HIGHLIGHT_BIT_COLOR;
}
/**
* Get the highlighted special bit. This works exactly like lochstreifen_get_highlight_region,
* returning FALSE (0) if there's no bit selected.
*
**/
int lochstreifen_get_highlight_bit(LOCHSTREIFEN *l, row_t *number_of_row, track_t *number_of_bit) {
if(l->highlight_bit_row == LOCHSTREIFEN_NO_ROW)
return 0;
if(number_of_row != NULL)
*number_of_row = l->highlight_bit_row;
if(number_of_bit != NULL)
*number_of_bit = l->highlight_bit_track;
return 1;
}
/**
* Remove the highlighting of the special bit.
**/
void lochstreifen_remove_highlight_bit(LOCHSTREIFEN *l) {
//l->highlight_bit_color = NULL; // stupid.
l->highlight_bit_row = LOCHSTREIFEN_NO_ROW; // more intelligent.
}
/**
* Whenever you change the matrix, call this function. It will just perform some
* cleanup work, espacially calculate the inverse matrix for the other-side calculations.
**/
void lochstreifen_check_matrix(LOCHSTREIFEN* l) {
l->matrix_inverse = l->matrix; // copy matrix
if(cairo_matrix_invert(&l->matrix_inverse) != CAIRO_STATUS_SUCCESS) { // invert the copy
// there are some matrizes which are not invertable
fprintf(stderr, "lochstreifen_check_matrix: Matrix is not invertable!\n");
}
}
/**
* If you want LOCHSTREIFEN only to paint a small area from the whole papertape, this
* is the function you can call.
* If you are using an affine transformation matrix for scaling/rotation/... purpose,
* this function will automatically transform these values. So don't give here values
* like "LOCHSTREIFEN_WIDTH" or "LOCHSTREIFEN_HEIGHT" ;-)
*
**/
void lochstreifen_set_clip(LOCHSTREIFEN *l, double x, double y, double width, double height) {
if(l->clip == NULL)
l->clip = malloc(sizeof(cairo_rectangle_t));
cairo_matrix_transform_point(&l->matrix_inverse, &x, &y);
cairo_matrix_transform_distance(&l->matrix_inverse, &width, &height);
l->clip->x = x;
l->clip->y = y;
l->clip->width = width;
l->clip->height = height;
}
/**
* I fyou want LOCHSTREIFEN no more to paint only a small area but the whole paper tape,
* simply call this function.
**/
void lochstreifen_remove_clip(LOCHSTREIFEN *l) {
free(l->clip);
l->clip = NULL; // not neccessary, I suppose.
}
/**
* Affect the current transformation matrix so the papertape finally has a length of
* exactly the value given in the argument, in pixels. The length depends from the
* number of rows on the papertape, so the length will increase if you increase the
* number of rows after calling lochstreifen_set_scaling_by_length.
**/
void lochstreifen_set_scaling_by_length(LOCHSTREIFEN *l, int length) {
double scale_factor = ((double)length) / LOCHSTREIFEN_LENGTH;
printf("Scaling to length=%i, having length=%f, getting %f as scale factor\n", length, LOCHSTREIFEN_LENGTH, scale_factor);
cairo_matrix_scale(&l->matrix, scale_factor, scale_factor);
lochstreifen_check_matrix(l);
}
void lochstreifen_set_scaling_by_width(LOCHSTREIFEN *l, int width) {
double scale_factor = ((double)width) / LOCHSTREIFEN_WIDTH;
cairo_matrix_scale(&l->matrix, scale_factor, scale_factor);
lochstreifen_check_matrix(l);
}
void lochstreifen_set_scaling_by_code_hole(LOCHSTREIFEN *l, int diameter) {
double scale_factor = ((double)diameter)/2 / LOCHSTREIFEN_RADIUS_CODE_HOLE;
cairo_matrix_scale(&l->matrix, scale_factor, scale_factor);
lochstreifen_check_matrix(l);
}
/**
* Get the dimensions of the paper tape in the output image. Be careful: While
* width/length are well defined in the LOCHSTREIFEN process, hereby width and
* height mean the dimensions of the rectangular surface picture.
* If you need only the value of one dimension, use
* lochstreifen_get_target_width or lochstreifen_get_target_height, respectively.
*
* The calculation of these values is quite expensive, so you do well when
* caching the results.
* @return nothing, because Call-by-Reference.
**/
void lochstreifen_get_target_dimensions(LOCHSTREIFEN *l, int *width, int *height) {
int i;
// preset the dimensions to 0
if(width != NULL) *width = 0;
if(height != NULL) *height = 0;
// define the "bounding box" where the papertape resides in
// user space cordinates (like lochstreifen_draw paints)
double x[] = { 0, LOCHSTREIFEN_LENGTH, 0, LOCHSTREIFEN_LENGTH };
double y[] = { 0, 0, LOCHSTREIFEN_WIDTH, LOCHSTREIFEN_WIDTH };
// transform the "bounding box" throught the matrix
for(i=0; i < (sizeof(x) / sizeof(double)); i++) {
cairo_matrix_transform_point(&l->matrix, &x[i], &y[i]);
// store biggest x/y values
if(width != NULL && x[i] > *width)
*width = x[i];
if(height != NULL && y[i] > *height)
*height = y[i];
}
}
/**
* Get width of the output picture. This is only a nicer frontend to
* lochstreifen_get_target_dimensions that returns the width value.
**/
int lochstreifen_get_target_width(LOCHSTREIFEN *l) {
int width;
lochstreifen_get_target_dimensions(l, &width, NULL);
return width;
}
/**
* Get height of the output picture. This is only a nicer frontend to
* lochstreifen_get_target_dimensions that returns the height value.
**/
int lochstreifen_get_target_height(LOCHSTREIFEN *l) {
int height;
lochstreifen_get_target_dimensions(l, NULL, &height);
return height;
}
/**
* Get a bit tuple from the positions on the target surface. This function
* translates the points x/y with the inverse current translation matrix. Then
* it calculates the row number that equals with the x coordinate and afterwards
* the track number that equals with the y coordinate.
*
* If you need only one dimension, set the other NULL.
*
* You can use lochstreifen_get_traget_row_from_point and
* lochstreifen_get_target_track_from_point if you need only one dimension. Furthermore
* they will nicely return the requested data -- no need for call by reference.
*
* @param bit The target lochstreifen_bit_t (call by reference). Should not be NULL.
* @param x The x coordinate you want to check
* @param y The y coordinate you want to check
**/
void lochstreifen_get_target_bit_from_point(LOCHSTREIFEN* l, row_t* row, track_t* track, double x, double y) {
cairo_matrix_transform_point(&l->matrix_inverse, &x, &y); // transform points
if(y < 0 || y > LOCHSTREIFEN_WIDTH) {
// we are not at data, at all
if(row != NULL)
*row = LOCHSTREIFEN_NO_ROW;
if(track != NULL)
*track = LOCHSTREIFEN_NO_TRACK;
return;
}
if(row == NULL) {
// if only the track was requested, create a temporary row buffer, because
// we need the row number as a basis for the correct track calculation.
row_t row_buffer;
row = &row_buffer;
}
// Row calculation is fairly easy:
if(x < LOCHSTREIFEN_TEAR_OFF_LENGTH || x > (LOCHSTREIFEN_LENGTH - LOCHSTREIFEN_TEAR_OFF_LENGTH)) {
// we are not at data
*row = LOCHSTREIFEN_NO_ROW;
} else {
// getting the row from the x value. This is pretty much the same algorithm
// like used in papertape_draw for checking up the rectangle bounding box (clipping).
*row = floor(
(x - LOCHSTREIFEN_TEAR_OFF_LENGTH) / LOCHSTREIFEN_ROW_DISTANCE
);
}
// Track calculation is much more complex:
if(track != NULL) {
/* get the track from the y value. This is not simply something like
* *track = floor( y / LOCHSTREIFEN_TRACK_DISTANCE );
* because we must take care that a track is a round thing with a
* specific height.
*/
double M_x, M_y; // the centers of the circles
int i; // some iterator
// The center of this circle, fairly complex to calculate, see lochstreifen_draw.
M_x = LOCHSTREIFEN_TEAR_OFF_LENGTH + (*row + 0.5) * LOCHSTREIFEN_ROW_DISTANCE;
M_y = LOCHSTREIFEN_TRACK_DISTANCE; // the first track, to get onto papertape
// this a genious canidate for loop unrolling:
for(i=0; i < 8; i++) {
// simple linear algebra: Check if point is in the circle.
//fprintf(stderr, "bit %d: (%f|%f) <-> P(%f|%f) <= %f\n", i, M_x, M_y, x, y, LOCHSTREIFEN_RADIUS_CODE_HOLE);
if(hypot( (x-M_x) , (y-M_y) ) <= LOCHSTREIFEN_RADIUS_CODE_HOLE) {
// the point is in this circle, so we have found the correct track.
//fprintf(stderr, "FOUND %d\n", i);
*track = i;
return;
} else {
// go on, set M for next circle
M_y += LOCHSTREIFEN_TRACK_DISTANCE;
// jump over feed track before i == 3.
if(i == 2)
M_y += LOCHSTREIFEN_TRACK_DISTANCE;
}
} // for tracks
// cursor not over circle:
*track = LOCHSTREIFEN_NO_TRACK;
//fprintf(stderr, "NOT FOUND TRACK.\n");
} // y dimension
} // lochstreifen_get_target_bit_on_point
/**
* Get the row number from an x/y target coordinate. That is: At first, translate
* the points with the current translation matrix, afterwards calculate the row
* number for the position.
*
* @returns Row number, counting from zero. Negative value if point not on papertape data chunk.
**/
row_t lochstreifen_get_target_row_from_point(LOCHSTREIFEN* l, double x, double y) {
row_t row;
lochstreifen_get_target_bit_from_point(l, &row, NULL, x, y);
return row;
}
track_t lochstreifen_get_target_track_from_point(LOCHSTREIFEN* l, double x, double y) {
track_t track;
lochstreifen_get_target_bit_from_point(l, NULL, &track, x, y);
return track;
}
/**
* Calculate the rectangle that contains the complete row on the LOCHSTREIFEN,
* in target surface dimensions (using the transformation matrix). If the operation
* fails (if the row doesn't exist), the rect is not touched at all.
*
* @param row Number of Row you want
* @param rect Call by reference like rectangle type
* @return LOCHSTREIFEN_SUCCESS or LOCHSTREIFEN_NO_ROW.
**/
int lochstreifen_get_target_rect_from_row(LOCHSTREIFEN* l, row_t row, cairo_rectangle_t* rect) {
// check wheter we are in bounds
if(row < 0 || row > l->data_length)
return LOCHSTREIFEN_NO_ROW;
// This rectangle is a bit bigger than the real track, so that outlines, etc.
// could be drawn there, too.
rect->x = LOCHSTREIFEN_TEAR_OFF_LENGTH + (row) * LOCHSTREIFEN_ROW_DISTANCE;
rect->y = 0;
rect->width = LOCHSTREIFEN_ROW_DISTANCE;
rect->height = LOCHSTREIFEN_WIDTH;
// take this as padding:
#define TARGET_RECT_HOW_MUCH_BIGGER (LOCHSTREIFEN_ROW_DISTANCE * 0.1)
rect->x -= TARGET_RECT_HOW_MUCH_BIGGER;
rect->y -= TARGET_RECT_HOW_MUCH_BIGGER;
rect->width += 2*TARGET_RECT_HOW_MUCH_BIGGER;
rect->height += 2*TARGET_RECT_HOW_MUCH_BIGGER;
// transform the points
cairo_matrix_transform_point(&l->matrix, &rect->x, &rect->y);
cairo_matrix_transform_distance(&l->matrix, &rect->width, &rect->height);
return LOCHSTREIFEN_SUCCESS;
}
/**
* This is just the same like lochstreifen_get_target_rect_from_row, just with
* bits. This is still a rectangle which contains the complete circle that represents
* the bit, useful e.g. for GDK expose events.
*
* @param row Number of Row
* @param track Number of Track
* @param rect Call by reference like rectangle type
* @return LOCHSTREIFEN_SUCCESS or LOCHSTREIFEN_NO_ROW or LOCHSTREIFEN_NO_TRACK
**/
int lochstreifen_get_target_rect_from_bit(LOCHSTREIFEN* l, row_t row, track_t track, cairo_rectangle_t* rect) {
// check bounds
if(row < 0 || row > l->data_length)
return LOCHSTREIFEN_NO_ROW;
if(track < 0 || track > 7)
return LOCHSTREIFEN_NO_TRACK;
// the rectangle is doesn't touch the circle, but leaves quite a bit space around
// it, so outlines, etc. should not be a problem.
// this code is quite like the beginning x/y dimensions in lochstreifen_draw():
rect->x = LOCHSTREIFEN_TEAR_OFF_LENGTH + (row) * LOCHSTREIFEN_ROW_DISTANCE;
//+ (LOCHSTREIFEN_ROW_DISTANCE - LOCHSTREIFEN_RADIUS_CODE_HOLE*2) / 2; // the center
rect->y = LOCHSTREIFEN_TRACK_DISTANCE * (track + (track > 2 ? 1 : 0) );
// The correct values for width/height would be:
// rect->width = LOCHSTREIFEN_RADIUS_CODE_HOLE * 2;
// but that won't at exposures if the hole is painted e.g. with an outline (cairo_stroke).
// We therefore simply take the complete track as width
rect->width = LOCHSTREIFEN_ROW_DISTANCE;
rect->height = LOCHSTREIFEN_TRACK_DISTANCE * 2;
// transform to user space
cairo_matrix_transform_point(&l->matrix, &rect->x, &rect->y);
cairo_matrix_transform_distance(&l->matrix, &rect->width, &rect->height);
return LOCHSTREIFEN_SUCCESS;
}
/**
*
* Just to be sure that there are no misunderstandings: Rotation (wiht cairo)
* principally works like this:
*
* | B----D
* | V |
* 180° | | V |
* D--------C | V | 270°
* |>>>>>>>>| | | V |
* B--------A A----C
* |
* - - - - - -+------------------------> x axis
* | .
* C----A | A------------B .
* | ^ | | |<<<<<<<<<<<<| .
* 90° | ^ | | C------------D .
* | ^ | | original . <- borders of the target surface
* | ^ | |....................+
* D----B |
* V
* y axis
*
* Unfortunately every rotated box would be outside of the target surface
* (e.g. an 200x100px PNG image), thus we must move the matrix inside the
* positive x/y axis intercept.
* This can be performed quite simple
*
**/
void lochstreifen_set_rotation(LOCHSTREIFEN *l, enum lochstreifen_direction direction) {
switch(direction) {
case LOCHSTREIFEN_MOVEMENT_TO_LEFT:
// standard
case LOCHSTREIFEN_MOVEMENT_TO_TOP:
//cairo_matrix_rotate();
case LOCHSTREIFEN_MOVEMENT_TO_RIGHT:
case LOCHSTREIFEN_MOVEMENT_TO_BOTTOM:
case LOCHSTREIFEN_MOVEMENT_NONE:
default:
break;
}
lochstreifen_check_matrix(l);
}
/**
* The very central main function for the LOCHSTREIFEN object: Drawing.
* You are supposed to create an adequate cairo context.
*
* This basic drawing function will draw relative to the width of the
* paper tape, like defined as 1 inch in the ECMA-10 standard. This
* width will have the value "1".
*
* Your cropping/... blub shall treat hereby. blabla.
**/
void lochstreifen_draw(LOCHSTREIFEN *l, cairo_t *cr) {
// complete length of paper tape:
double length = LOCHSTREIFEN_LENGTH;
// iterators for the byte/bit loops
int row,track;
// position while iterating
double x,y;
// max positions to iterate to
int row_max, track_min, track_max;
// apply the matrix!
cairo_set_matrix(cr, &l->matrix);
// check if we need to repaint the papertape
if(l->clip != NULL && (l->clip->y > LOCHSTREIFEN_WIDTH || l->clip->x > LOCHSTREIFEN_LENGTH) ) {
// Clipping region is outside of papertape!
//printf("draw: Outside papertape.\n");
return;
}
// paint outer background color.
if(l->outer_background_color != NULL) {
cairo_set_source(cr, l->outer_background_color);
cairo_paint(cr);
}
// paint the paper tape outline, including the tear-off
if(l->papertape_background_color != NULL) {
cairo_set_source(cr, l->papertape_background_color);
// tear-off at the beginning (0.5 of paper tape width)
cairo_move_to(cr, LOCHSTREIFEN_TEAR_OFF_LENGTH * 0.8, 0);
cairo_line_to(cr, 0, 4*LOCHSTREIFEN_TRACK_DISTANCE);
cairo_line_to(cr, LOCHSTREIFEN_TEAR_OFF_LENGTH, LOCHSTREIFEN_WIDTH);
cairo_line_to(cr, LOCHSTREIFEN_TEAR_OFF_LENGTH, 0);
cairo_close_path(cr);
cairo_fill(cr);
// long rectangular area over complete paper tape
if(l->clip == NULL) {
cairo_rectangle(cr, LOCHSTREIFEN_TEAR_OFF_LENGTH, 0.0,
length - 2*LOCHSTREIFEN_TEAR_OFF_LENGTH, LOCHSTREIFEN_WIDTH);
// irc.gnome.org, #gtk+:
// <owen> sven__: a basic comment is that if you draw a *single* shape with cairo
// that's bigger than 32k pixels in any dimension, it's unlikely to work
// [17:51] <owen> sven__: for the Xlib backend. this is a lot more fixable now
// that Cairo switched to 24.8 fixed point internally, but I don't think anybody
// has done the work to fix it
} else {
// calculate the neccessary width
// if clipping region exceeds lochstreifen width.... make it shorter, else let it.
double width = ( (l->clip->x + l->clip->width) > (LOCHSTREIFEN_LENGTH - LOCHSTREIFEN_TEAR_OFF_LENGTH) )
? ( LOCHSTREIFEN_LENGTH - LOCHSTREIFEN_TEAR_OFF_LENGTH - l->clip->x )
: l->clip->width;
// don't have the clipping region paint in the tear off
// right, so crop it at the left.
if(l->clip->x < LOCHSTREIFEN_TEAR_OFF_LENGTH)
width -= LOCHSTREIFEN_TEAR_OFF_LENGTH;
cairo_rectangle(cr,
(l->clip->x >= LOCHSTREIFEN_TEAR_OFF_LENGTH) ?
l->clip->x : LOCHSTREIFEN_TEAR_OFF_LENGTH,
0,
width,
LOCHSTREIFEN_WIDTH);
}
cairo_fill(cr);
// tear-off at the end (0.5 of paper tape width)
cairo_move_to(cr, length-LOCHSTREIFEN_TEAR_OFF_LENGTH, 0);
cairo_line_to(cr, length-LOCHSTREIFEN_TEAR_OFF_LENGTH, LOCHSTREIFEN_WIDTH);
cairo_line_to(cr, length, LOCHSTREIFEN_WIDTH);
cairo_line_to(cr, length-LOCHSTREIFEN_TEAR_OFF_LENGTH, 4*LOCHSTREIFEN_TRACK_DISTANCE);
cairo_line_to(cr, length-LOCHSTREIFEN_TEAR_OFF_LENGTH * 0.2, 0);
cairo_close_path(cr);
cairo_fill(cr);
} // paper tape background finished.
// paint feed holes in the tear-off at the beginning
// only if in clipped area
if( ( l->clip == NULL || (l->clip->x <= LOCHSTREIFEN_TEAR_OFF_LENGTH) )
&& (l->feed_hole_color != NULL) ) {
cairo_set_source(cr, l->feed_hole_color);
for(y=4*LOCHSTREIFEN_TRACK_DISTANCE,
x=LOCHSTREIFEN_TEAR_OFF_LENGTH - LOCHSTREIFEN_ROW_DISTANCE/2;
x > 0; x -= LOCHSTREIFEN_ROW_DISTANCE)
cairo_arc(cr, x, y, LOCHSTREIFEN_RADIUS_FEED_HOLE, 0., 2*M_PI);
cairo_fill(cr);
}
if(l->clip != NULL) {
/* calculate clipping in dimension X (rows, bytes).
* Clipping in this dimension is very important, especially at very long
* papertapes. Application software (like GtkPaperTape) usually zooms the
* papertape, so it won't fit completely on the screen. So skipping bytes
* which are not displayed is very performant.
*/
if(l->clip->x < LOCHSTREIFEN_TEAR_OFF_LENGTH) {
// begin before tear off
row = 0;
row_max = floor(
( l->clip->width - (l->clip->x - LOCHSTREIFEN_TEAR_OFF_LENGTH) )
/ LOCHSTREIFEN_ROW_DISTANCE
);
} else {
// after tear off
row = floor (
(l->clip->x - LOCHSTREIFEN_TEAR_OFF_LENGTH) / LOCHSTREIFEN_ROW_DISTANCE
);
// end with just one more row as neccessary
row_max = row + 1 + ceil(
( l->clip->width ) / LOCHSTREIFEN_ROW_DISTANCE
);
}
// don't go over the data range
if(row_max > l->data_length)
row_max = l->data_length;
// dont' have row_max smaller than row
//if(row_max < row)
// row_max = row;
/* calculate clipping in dimension Y (tracks, bits)
* Y clipping is not as important, because there are only 9 circles
* to paint. The feed hole (which is painted just in the loop for the
* 3. track hole is not counted, therefore the calculations become somewhat
* inneccessary complex, compared to the painting overhead for the complete
* 8 tracks instead of e.g. only 6 tracks. Apart from that, application software
* usually doesn't clip the papertape this way, because users usually want to
* see a complete byte and not only a half byte.
*/
// calculation of first track to begin with.
/*
track_min = l->clip->y == 0 ? 0 : floor( l->clip->y / LOCHSTREIFEN_TRACK_DISTANCE );
// calculation of last track to end with.
if(l->clip->y + l->clip->height > LOCHSTREIFEN_WIDTH)
track_max = 8; // clipping region is *much* bigger than the papertape width
else {
// just paint one more than neccessary
track_max = 1 + 8 - floor(
(LOCHSTREIFEN_WIDTH - (l->clip->y + l->clip->height))
/ LOCHSTREIFEN_TRACK_DISTANCE
);
// dont't go over the track range
if(track_max > 8)
track_max = 8;
}*/
track_min = 0;
track_max = 8;
} else {
// no clipping. Paint *everything*.
row = 0;
row_max = l->data_length;
track_min = 0;
track_max = 8;
}
if(l->clip != NULL) {
double x,y,width,height;
x=l->clip->x; y=l->clip->y; width=l->clip->width; height=l->clip->height;
cairo_user_to_device(cr, &x, &y);
cairo_user_to_device_distance(cr, &width, &height);
printf("draw: %i|%i %ix%i ", (int)x
, (int)y
, (int)width
, (int)height
); printf("row %i->%i track %i->%i\n", row
, row_max
, track_min
, track_max
);
// loop all the rows (bytes)
// x start: tear off + one half row offset + offset rows * length of row
for(x=LOCHSTREIFEN_TEAR_OFF_LENGTH + LOCHSTREIFEN_ROW_DISTANCE/2 + row*LOCHSTREIFEN_ROW_DISTANCE;
row < row_max; row++, x += LOCHSTREIFEN_ROW_DISTANCE) {
// call row callback
if(l->row_callback != NULL) {
printf("CALLING ROW CALLBACK!\n"); (*l->row_callback) (&row, cr, l->row_callback_user_data);
// at least now that's all the magic ;)
}
// highlight region?
if(l->highlight_region_color != NULL && l->highlight_region_start != LOCHSTREIFEN_NO_ROW &&
(row >= l->highlight_region_start || row < l->highlight_region_end) ) {
cairo_set_source(cr, l->highlight_region_color);
// hier was schlaues schickeres als ein RECHTECK
// einfallen lassen. Vielleicht mit abgerundetem
// Rechteck? Wuerde zu den Löchern passen.
// http://www.cairographics.org/cookbook/roundedrectangles/
cairo_rectangle(cr, x - LOCHSTREIFEN_ROW_DISTANCE / 2, 0,
LOCHSTREIFEN_TRACK_DISTANCE * (l->highlight_region_end - l->highlight_region_start),
LOCHSTREIFEN_WIDTH);
cairo_fill(cr);
}
// highlight (only) this row?
if(l->highlight_row_color != NULL && row == l->highlight_row_number) {
// if check again LOCHSTREIFEN_NO_ROW not neccessary because row iterator never
// gets LOCHSTREIFEN_NO_ROW.
cairo_set_source(cr, l->highlight_row_color);
// genauso abgerundetes Rechteck wie bei der region
// überlegen.
cairo_rectangle(cr, x - LOCHSTREIFEN_ROW_DISTANCE/2, 0,
LOCHSTREIFEN_TRACK_DISTANCE, LOCHSTREIFEN_WIDTH);
cairo_fill(cr);
}
// loop all the tracks (bits)
// y start: offset tracks * width of track + one track (to get onto the papertape)
for(track=track_min, y = LOCHSTREIFEN_TRACK_DISTANCE*(track+1);
track < track_max; track++, y += LOCHSTREIFEN_TRACK_DISTANCE) {
if(track == 3) {
// the feed hole is at position 3
if(l->feed_hole_color != NULL) {
// we paint a feed hole :-)
cairo_set_source(cr, l->feed_hole_color);
cairo_arc(cr, x, y, LOCHSTREIFEN_RADIUS_FEED_HOLE, 0., 2*M_PI);
cairo_fill(cr);
}
// jump one track, in every case.
y += 0.1;
}
if( ((l->data[row] >> track) & 0x01) != 0x01) {
// bit is logical ZERO (0)
if(l->zero_code_hole_color != NULL) {
// we want to paint zeros.
cairo_set_source(cr, l->zero_code_hole_color);
cairo_arc(cr, x, y, LOCHSTREIFEN_RADIUS_CODE_HOLE, 0., 2*M_PI);
cairo_fill(cr);
}
} else if(l->one_code_hole_color != NULL) {
// we want to paint logical ONEs.
cairo_set_source(cr, l->one_code_hole_color);
cairo_arc(cr, x, y, LOCHSTREIFEN_RADIUS_CODE_HOLE, 0., 2*M_PI);
cairo_fill(cr);
}
if(l->highlight_bit_row == row && l->highlight_bit_track == track
&& l->highlight_bit_color != NULL) {
// if check against l->highlight_bit_row == LOCHSTREIFEN_NO_ROW
// not neccessary because row never gets LOCHSTREIFEN_NO_ROW.
// eigentlich wollen wir hier eine Umrahmung zeichnen,
// damit der Zustand (0/1) nicht unsichtbar wird. Jetzt
// erst mal aber die Billigversion mit der dümmlichen
// Vollfarbe ;-)
cairo_set_source(cr, l->highlight_bit_color);
cairo_arc(cr, x, y, LOCHSTREIFEN_RADIUS_CODE_HOLE, 0., 2*M_PI);
cairo_set_line_width(cr, 0.01);
cairo_stroke(cr); // mal testen :-)
}
} // for tracks (bits)
} // for rows (bytes)
} // function lochstreifen_draw