#include /* * Remove (mask ... white) until we get something not white */ static int iswhitenode(fz_colornode *node) { if (!strcmp(node->cs->name, "DeviceGray")) return fabs(node->samples[0] - 1.0) < FLT_EPSILON; if (!strcmp(node->cs->name, "DeviceRGB")) return fabs(node->samples[0] - 1.0) < FLT_EPSILON && fabs(node->samples[1] - 1.0) < FLT_EPSILON && fabs(node->samples[2] - 1.0) < FLT_EPSILON; if (!strcmp(node->cs->name, "DeviceCMYK")) return fabs(node->samples[0]) < FLT_EPSILON && fabs(node->samples[1]) < FLT_EPSILON && fabs(node->samples[2]) < FLT_EPSILON && fabs(node->samples[3]) < FLT_EPSILON; return 0; } static int cleanwhite(fz_node *node) { fz_node *current; fz_node *next; fz_node *shape; fz_node *color; for (current = node->first; current; current = next) { next = current->next; if (fz_islinknode(current)) return 1; else if (fz_isimagenode(current)) return 1; else if (fz_isshadenode(current)) return 1; else if (fz_iscolornode(current)) { if (!iswhitenode((fz_colornode*)current)) return 1; } else if (fz_ismasknode(current)) { shape = current->first; color = shape->next; if (fz_iscolornode(color)) { if (iswhitenode((fz_colornode*)color)) fz_removenode(current); else return 1; } else { if (cleanwhite(current)) return 1; } } else { if (cleanwhite(current)) return 1; } } return 0; } /* * Remove useless overs that only have one child. */ static void cleanovers(fz_node *node) { fz_node *prev; fz_node *next; fz_node *current; fz_node *child; prev = nil; for (current = node->first; current; current = next) { next = current->next; if (fz_isovernode(current)) { if (current->first == current->last) { child = current->first; fz_removenode(current); if (child) { if (prev) fz_insertnodeafter(prev, child); else fz_insertnodefirst(node, child); } current = child; } } if (current) prev = current; } for (current = node->first; current; current = current->next) cleanovers(current); } /* * Remove rectangular clip-masks whose contents fit... */ static int getrect(fz_pathnode *path, fz_rect *bboxp) { float x, y, w, h; /* move x y, line x+w y, line x+w y+h, line x y+h, close */ if (path->len != 13) return 0; if (path->els[0].k != FZ_MOVETO) return 0; x = path->els[1].v; y = path->els[2].v; if (path->els[3].k != FZ_LINETO) return 0; w = path->els[4].v - x; if (path->els[5].v != y) return 0; if (path->els[6].k != FZ_LINETO) return 0; if (path->els[7].v != x + w) return 0; h = path->els[8].v - y; if (path->els[9].k != FZ_LINETO) return 0; if (path->els[10].v != x) return 0; if (path->els[11].v != y + h) return 0; if (path->els[12].k != FZ_CLOSEPATH) return 0; bboxp->min.x = MIN(x, x + w); bboxp->min.y = MIN(y, y + h); bboxp->max.x = MAX(x, x + w); bboxp->max.y = MAX(y, y + h); return 1; } static int fitsinside(fz_node *node, fz_rect clip) { fz_rect bbox; bbox = fz_boundnode(node, fz_identity()); if (fz_isinfiniterect(bbox)) return 0; if (fz_isemptyrect(bbox)) return 1; if (bbox.min.x < clip.min.x) return 0; if (bbox.max.x > clip.max.x) return 0; if (bbox.min.y < clip.min.y) return 0; if (bbox.max.y > clip.max.y) return 0; return 1; } static void cleanmasks(fz_node *node) { fz_node *prev; fz_node *current; fz_node *shape; fz_node *color; fz_rect bbox; prev = nil; for (current = node->first; current; current = current->next) { retry: if (fz_ismasknode(current)) { shape = current->first; color = shape->next; if (color == nil) { fz_removenode(current); prev = nil; current = node->first; goto retry; } if (fz_ispathnode(shape)) { if (getrect((fz_pathnode*)shape, &bbox)) { if (fitsinside(color, bbox)) { fz_removenode(current); if (prev) fz_insertnodeafter(prev, color); else fz_insertnodefirst(node, color); current = color; goto retry; } } } } prev = current; } for (current = node->first; current; current = current->next) cleanmasks(current); } /* * Turn 1x1 images into rectangle fills */ static fz_error *clean1x1(fz_node *node) { fz_error *error; fz_node *current; fz_node *color; fz_pathnode *rect; fz_node *mask; fz_image *image; fz_pixmap *pix; float v[FZ_MAXCOLORS]; int i; for (current = node->first; current; current = current->next) { if (fz_isimagenode(current)) { image = ((fz_imagenode*)current)->image; if (image->w == 1 && image->h == 1) { error = fz_newpathnode(&rect); fz_moveto(rect, 0, 0); fz_lineto(rect, 1, 0); fz_lineto(rect, 1, 1); fz_lineto(rect, 0, 1); fz_closepath(rect); fz_endpath(rect, FZ_FILL, nil, nil); if (image->cs) { error = fz_newpixmap(&pix, 0, 0, 1, 1, image->n + 1); if (error) return error; error = image->loadtile(image, pix); if (error) return error; for (i = 0; i < image->n; i++) v[i] = pix->samples[i + 1] / 255.0; fz_droppixmap(pix); error = fz_newcolornode(&color, image->cs, image->n, v); if (error) return error; error = fz_newmasknode(&mask); if (error) return error; fz_insertnodeafter(mask, (fz_node*)rect); fz_insertnodeafter(mask, color); fz_insertnodeafter(current, mask); fz_removenode(current); current = color; } else { /* pray that the 1x1 image mask is all opaque */ fz_insertnodeafter(current, (fz_node*)rect); fz_removenode(current); current = (fz_node*)rect; } } } error = clean1x1(current); if (error) return error; } return nil; } /* * */ fz_error * fz_optimizetree(fz_tree *tree) { if (getenv("DONTOPT")) return nil; cleanwhite(tree->root); cleanovers(tree->root); cleanmasks(tree->root); clean1x1(tree->root); return nil; }