static void fz_draw_clip_path(fz_device *devp, fz_path *path, fz_rect rect, int even_odd, fz_matrix ctm) { fz_draw_device *dev = devp->user; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; fz_bbox bbox; fz_draw_state *state = &dev->stack[dev->top]; fz_colorspace *model; fz_context *ctx = dev->ctx; fz_reset_gel(dev->gel, state->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); state = push_stack(dev); model = state->dest->colorspace; bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, state->scissor); bbox = fz_intersect_bbox(bbox, fz_bbox_from_rect(rect)); /* SumatraPDF: try to match rendering with and without display list */ if (fz_is_infinite_rect(rect)) bbox = fz_intersect_bbox(bbox, fz_bbox_from_rect(fz_bound_path(ctx, path, NULL, ctm))); if (fz_is_empty_rect(bbox) || fz_is_rect_gel(dev->gel)) { state[1].scissor = bbox; state[1].mask = NULL; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, "Clip (rectangular) begin\n"); #endif return; } fz_try(ctx) { state[1].mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].mask); state[1].dest = fz_new_pixmap_with_bbox(dev->ctx, model, bbox); fz_clear_pixmap(dev->ctx, state[1].dest); if (state[1].shape) { state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].shape); } fz_scan_convert(dev->gel, even_odd, bbox, state[1].mask, NULL); state[1].blendmode |= FZ_BLEND_ISOLATED; state[1].scissor = bbox; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, "Clip (non-rectangular) begin\n"); #endif } fz_catch(ctx) { emergency_pop_stack(dev, state); } }
static void svg_dev_stroke_state(fz_context *ctx, svg_device *sdev, fz_stroke_state *stroke_state, const fz_matrix *ctm) { fz_output *out = sdev->out; float exp; exp = fz_matrix_expansion(ctm); if (exp == 0) exp = 1; fz_printf(ctx, out, " stroke-width=\"%g\"", stroke_state->linewidth/exp); fz_printf(ctx, out, " stroke-linecap=\"%s\"", (stroke_state->start_cap == FZ_LINECAP_SQUARE ? "square" : (stroke_state->start_cap == FZ_LINECAP_ROUND ? "round" : "butt"))); if (stroke_state->dash_len != 0) { int i; fz_printf(ctx, out, " stroke-dasharray="); for (i = 0; i < stroke_state->dash_len; i++) fz_printf(ctx, out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]); fz_printf(ctx, out, "\""); if (stroke_state->dash_phase != 0) fz_printf(ctx, out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase); } if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS) fz_printf(ctx, out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit); fz_printf(ctx, out, " stroke-linejoin=\"%s\"", (stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" : (stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter"))); }
static void fz_draw_fill_path(void *user, fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; unsigned char colorbv[FZ_MAX_COLORS + 1]; float colorfv[FZ_MAX_COLORS]; fz_bbox bbox; int i; fz_reset_gel(dev->gel, dev->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (fz_is_empty_rect(bbox)) return; fz_convert_color(colorspace, color, model, colorfv); for (i = 0; i < model->n; i++) colorbv[i] = colorfv[i] * 255; colorbv[i] = alpha * 255; fz_scan_convert(dev->gel, even_odd, bbox, dev->dest, colorbv); }
static void fz_draw_clip_stroke_path(fz_device *devp, fz_path *path, fz_rect *rect, fz_stroke_state *stroke, fz_matrix ctm) { fz_draw_device *dev = devp->user; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; float linewidth = stroke->linewidth; fz_bbox bbox; fz_draw_state *state = &dev->stack[dev->top]; fz_colorspace *model; fz_context *ctx = dev->ctx; if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; fz_reset_gel(dev->gel, state->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); state = push_stack(dev); model = state->dest->colorspace; bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, state->scissor); if (rect) bbox = fz_intersect_bbox(bbox, fz_bbox_covering_rect(*rect)); /* SumatraPDF: try to match rendering with and without display list */ else bbox = fz_intersect_bbox(bbox, fz_bbox_covering_rect(fz_bound_path(ctx, path, stroke, ctm))); fz_try(ctx) { state[1].mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].mask); state[1].dest = fz_new_pixmap_with_bbox(dev->ctx, model, bbox); fz_clear_pixmap(dev->ctx, state[1].dest); if (state->shape) { state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].shape); } if (!fz_is_empty_rect(bbox)) fz_scan_convert(dev->gel, 0, bbox, state[1].mask, NULL); state[1].blendmode |= FZ_BLEND_ISOLATED; state[1].scissor = bbox; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, "Clip (stroke) begin\n"); #endif } fz_catch(ctx) { emergency_pop_stack(dev, state); } }
static void fz_draw_stroke_path(fz_device *devp, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_draw_device *dev = devp->user; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; float linewidth = stroke->linewidth; unsigned char colorbv[FZ_MAX_COLORS + 1]; float colorfv[FZ_MAX_COLORS]; fz_bbox bbox; int i; fz_draw_state *state = &dev->stack[dev->top]; fz_colorspace *model = state->dest->colorspace; if (model == NULL) model = fz_device_gray; if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; fz_reset_gel(dev->gel, state->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, state->scissor); if (fz_is_empty_rect(bbox)) return; if (state->blendmode & FZ_BLEND_KNOCKOUT) state = fz_knockout_begin(dev); fz_convert_color(dev->ctx, model, colorfv, colorspace, color); for (i = 0; i < model->n; i++) colorbv[i] = colorfv[i] * 255; colorbv[i] = alpha * 255; fz_scan_convert(dev->gel, 0, bbox, state->dest, colorbv); if (state->shape) { fz_reset_gel(dev->gel, state->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); colorbv[0] = 255; fz_scan_convert(dev->gel, 0, bbox, state->shape, colorbv); } if (state->blendmode & FZ_BLEND_KNOCKOUT) fz_knockout_end(dev); }
static void fz_draw_clip_stroke_path(fz_context *ctx, void *user, fz_path *path, fz_rect *rect, fz_stroke_state *stroke, fz_matrix ctm) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; float linewidth = stroke->linewidth; fz_pixmap *mask, *dest, *shape; fz_bbox bbox; if (dev->top == dev->stack_max) fz_grow_stack(dev); if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; fz_reset_gel(dev->gel, dev->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(ctx, dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(ctx, dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (rect) bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect)); mask = fz_new_pixmap_with_rect(ctx, NULL, bbox); fz_clear_pixmap(mask); dest = fz_new_pixmap_with_rect(ctx, model, bbox); /* FIXME: See note #1 */ fz_clear_pixmap(dest); if (dev->shape) { shape = fz_new_pixmap_with_rect(ctx, NULL, bbox); fz_clear_pixmap(shape); } else shape = NULL; if (!fz_is_empty_rect(bbox)) fz_scan_convert(dev->gel, 0, bbox, mask, NULL); dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = mask; dev->stack[dev->top].dest = dev->dest; dev->stack[dev->top].shape = dev->shape; /* FIXME: See note #1 */ dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED; dev->scissor = bbox; dev->dest = dest; dev->shape = shape; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top, "Clip (stroke) begin\n"); #endif dev->top++; }
static void svg_dev_text(svg_device *sdev, const fz_matrix *ctm, fz_text *text) { fz_output *out = sdev->out; int i; fz_matrix inverse; fz_matrix local_trm; float size; /* Rely on the fact that trm.{e,f} == 0 */ size = fz_matrix_expansion(&text->trm); local_trm.a = text->trm.a / size; local_trm.b = text->trm.b / size; local_trm.c = -text->trm.c / size; local_trm.d = -text->trm.d / size; local_trm.e = 0; local_trm.f = 0; fz_invert_matrix(&inverse, &local_trm); fz_concat(&local_trm, &local_trm, ctm); fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"", local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f); fz_printf(out, " font-size=\"%g\"", size); fz_printf(out, " font-family=\"%s\"", text->font->name); fz_printf(out, " x="); for (i=0; i < text->len; i++) { fz_text_item *it = &text->items[i]; fz_point p; p.x = it->x; p.y = it->y; fz_transform_point(&p, &inverse); fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.x); } fz_printf(out, "\" y="); for (i=0; i < text->len; i++) { fz_text_item *it = &text->items[i]; fz_point p; p.x = it->x; p.y = it->y; fz_transform_point(&p, &inverse); fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.y); } fz_printf(out, "\">\n"); for (i=0; i < text->len; i++) { fz_text_item *it = &text->items[i]; int c = it->ucs; if (c >= 32 && c <= 127 && c != '<' && c != '&') fz_printf(out, "%c", c); else fz_printf(out, "&#x%04x;", c); } fz_printf(out, "\n</text>\n"); }
static void fz_process_mesh_type3(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter) { fz_point p0, p1; float r0, r1; fz_point e; float er, rs; int count; p0.x = shade->u.l_or_r.coords[0][0]; p0.y = shade->u.l_or_r.coords[0][1]; r0 = shade->u.l_or_r.coords[0][2]; p1.x = shade->u.l_or_r.coords[1][0]; p1.y = shade->u.l_or_r.coords[1][1]; r1 = shade->u.l_or_r.coords[1][2]; /* number of segments for a half-circle */ count = 4 * sqrtf(fz_matrix_expansion(ctm) * fz_max(r0, r1)); if (count < 3) count = 3; if (count > 1024) count = 1024; if (shade->u.l_or_r.extend[0]) { if (r0 < r1) rs = r0 / (r0 - r1); else rs = -HUGENUM; e.x = p0.x + (p1.x - p0.x) * rs; e.y = p0.y + (p1.y - p0.y) * rs; er = r0 + (r1 - r0) * rs; fz_paint_annulus(ctx, ctm, e, er, 0, p0, r0, 0, count, painter); } fz_paint_annulus(ctx, ctm, p0, r0, 0, p1, r1, 1, count, painter); if (shade->u.l_or_r.extend[1]) { if (r0 > r1) rs = r1 / (r1 - r0); else rs = -HUGENUM; e.x = p1.x + (p0.x - p1.x) * rs; e.y = p1.y + (p0.y - p1.y) * rs; er = r1 + (r0 - r1) * rs; fz_paint_annulus(ctx, ctm, p1, r1, 1, e, er, 1, count, painter); } }
static void fz_draw_clip_path(void *user, fz_path *path, fz_rect *rect, int even_odd, fz_matrix ctm) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; fz_pixmap *mask, *dest; fz_bbox bbox; if (dev->top == STACK_SIZE) { fz_warn("assert: too many buffers on stack"); return; } fz_reset_gel(dev->gel, dev->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (rect) { bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect)); } if (fz_is_empty_rect(bbox) || fz_is_rect_gel(dev->gel)) { dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = NULL; dev->stack[dev->top].dest = NULL; dev->scissor = bbox; dev->top++; return; } mask = fz_new_pixmap_with_rect(NULL, bbox); dest = fz_new_pixmap_with_rect(model, bbox); fz_clear_pixmap(mask); fz_clear_pixmap(dest); fz_scan_convert(dev->gel, even_odd, bbox, mask, NULL); dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = mask; dev->stack[dev->top].dest = dev->dest; dev->scissor = bbox; dev->dest = dest; dev->top++; }
static void fz_draw_clip_stroke_path(void *user, fz_path *path, fz_rect *rect, fz_stroke_state *stroke, fz_matrix ctm) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; float linewidth = stroke->linewidth; fz_pixmap *mask, *dest; fz_bbox bbox; if (dev->top == STACK_SIZE) { fz_warn("assert: too many buffers on stack"); return; } if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; fz_reset_gel(dev->gel, dev->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (rect) bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect)); mask = fz_new_pixmap_with_rect(NULL, bbox); dest = fz_new_pixmap_with_rect(model, bbox); fz_clear_pixmap(mask); fz_clear_pixmap(dest); if (!fz_is_empty_rect(bbox)) fz_scan_convert(dev->gel, 0, bbox, mask, NULL); dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = mask; dev->stack[dev->top].dest = dev->dest; dev->scissor = bbox; dev->dest = dest; dev->top++; }
static void fz_draw_clip_path(fz_device *devp, fz_path *path, fz_rect *rect, int even_odd, fz_matrix ctm) { fz_draw_device *dev = devp->user; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; fz_bbox bbox; fz_draw_state *state = push_stack(dev); fz_colorspace *model = state->dest->colorspace; fz_reset_gel(dev->gel, state->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, state->scissor); if (rect) bbox = fz_intersect_bbox(bbox, fz_bbox_covering_rect(*rect)); if (fz_is_empty_rect(bbox) || fz_is_rect_gel(dev->gel)) { state[1].scissor = bbox; state[1].mask = NULL; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, "Clip (rectangular) begin\n"); #endif return; } state[1].mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].mask); state[1].dest = fz_new_pixmap_with_bbox(dev->ctx, model, bbox); fz_clear_pixmap(dev->ctx, state[1].dest); if (state[1].shape) { state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, bbox); fz_clear_pixmap(dev->ctx, state[1].shape); } fz_scan_convert(dev->gel, even_odd, bbox, state[1].mask, NULL); state[1].blendmode |= FZ_BLEND_ISOLATED; state[1].scissor = bbox; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top-1, "Clip (non-rectangular) begin\n"); #endif }
static void fz_draw_fill_path(fz_device *devp, fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_draw_device *dev = devp->user; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; unsigned char colorbv[FZ_MAX_COLORS + 1]; float colorfv[FZ_MAX_COLORS]; fz_bbox bbox; int i; fz_draw_state *state = &dev->stack[dev->top]; fz_colorspace *model = state->dest->colorspace; fz_reset_gel(dev->gel, state->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, state->scissor); if (fz_is_empty_rect(bbox)) return; if (state->blendmode & FZ_BLEND_KNOCKOUT) state = fz_knockout_begin(dev); fz_convert_color(dev->ctx, model, colorfv, colorspace, color); for (i = 0; i < model->n; i++) colorbv[i] = colorfv[i] * 255; colorbv[i] = alpha * 255; fz_scan_convert(dev->gel, even_odd, bbox, state->dest, colorbv); if (state->shape) { fz_reset_gel(dev->gel, state->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); colorbv[0] = alpha * 255; fz_scan_convert(dev->gel, even_odd, bbox, state->shape, colorbv); } if (state->blendmode & FZ_BLEND_KNOCKOUT) fz_knockout_end(dev); }
static fz_text_style * fz_lookup_text_style(fz_context *ctx, fz_text_sheet *sheet, fz_text *text, const fz_matrix *ctm, fz_colorspace *colorspace, float *color, float alpha, fz_stroke_state *stroke) { float size = 1.0f; fz_font *font = text ? text->font : NULL; int wmode = text ? text->wmode : 0; if (ctm && text) { fz_matrix tm = text->trm; fz_matrix trm; tm.e = 0; tm.f = 0; fz_concat(&trm, &tm, ctm); size = fz_matrix_expansion(&trm); } return fz_lookup_text_style_imp(ctx, sheet, size, font, wmode, 0); }
float fz_subpixel_adjust(fz_context *ctx, fz_matrix *ctm, fz_matrix *subpix_ctm, unsigned char *qe, unsigned char *qf) { float size = fz_matrix_expansion(ctm); int q; float pix_e, pix_f, r; /* Quantise the subpixel positions. */ /* We never need more than 4 subpixel positions for glyphs - arguably * even that is too much. */ if (size >= 48) q = 0, r = 0.5f; else if (size >= 24) q = 128, r = 0.25f; else q = 192, r = 0.125f; /* Split translation into pixel and subpixel parts */ subpix_ctm->a = ctm->a; subpix_ctm->b = ctm->b; subpix_ctm->c = ctm->c; subpix_ctm->d = ctm->d; subpix_ctm->e = ctm->e + r; pix_e = floorf(subpix_ctm->e); subpix_ctm->e -= pix_e; subpix_ctm->f = ctm->f + r; pix_f = floorf(subpix_ctm->f); subpix_ctm->f -= pix_f; /* Quantise the subpixel part */ *qe = (int)(subpix_ctm->e * 256) & q; subpix_ctm->e = *qe / 256.0f; *qf = (int)(subpix_ctm->f * 256) & q; subpix_ctm->f = *qf / 256.0f; /* Reassemble the complete translation */ ctm->e = subpix_ctm->e + pix_e; ctm->f = subpix_ctm->f + pix_f; return size; }
static void fz_draw_stroke_path(void *user, fz_path *path, fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, float *color, float alpha) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; float linewidth = stroke->linewidth; unsigned char colorbv[FZ_MAX_COLORS + 1]; float colorfv[FZ_MAX_COLORS]; fz_bbox bbox; int i; if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; fz_reset_gel(dev->gel, dev->scissor); if (stroke->dash_len > 0) fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth); else fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (fz_is_empty_rect(bbox)) return; fz_convert_color(colorspace, color, model, colorfv); for (i = 0; i < model->n; i++) colorbv[i] = colorfv[i] * 255; colorbv[i] = alpha * 255; fz_scan_convert(dev->gel, 0, bbox, dev->dest, colorbv); }
static void fz_text_extract(fz_context *ctx, fz_text_device *dev, fz_text *text, const fz_matrix *ctm, fz_text_style *style) { fz_point *pen = &dev->point; fz_font *font = text->font; FT_Face face = font->ft_face; fz_matrix tm = text->trm; fz_matrix trm; float size; float adv; fz_rect rect; fz_point dir, ndir; fz_point delta, ndelta; float dist, dot; float ascender = 1; float descender = 0; int multi; int i, j, err; if (text->len == 0) return; if (font->ft_face) { fz_lock(ctx, FZ_LOCK_FREETYPE); err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72); if (err) fz_warn(ctx, "freetype set character size: %s", ft_error_string(err)); ascender = (float)face->ascender / face->units_per_EM; descender = (float)face->descender / face->units_per_EM; fz_unlock(ctx, FZ_LOCK_FREETYPE); } else if (font->t3procs && !fz_is_empty_rect(&font->bbox)) { ascender = font->bbox.y1; descender = font->bbox.y0; } rect = fz_empty_rect; /* SumatraPDF: TODO: make this depend on the per-glyph displacement vector */ if (text->wmode == 0) { dir.x = 1; dir.y = 0; } else { dir.x = 0; dir.y = 1; } tm.e = 0; tm.f = 0; fz_concat(&trm, &tm, ctm); fz_transform_vector(&dir, &trm); dist = sqrtf(dir.x * dir.x + dir.y * dir.y); ndir.x = dir.x / dist; ndir.y = dir.y / dist; size = fz_matrix_expansion(&trm); for (i = 0; i < text->len; i++) { /* Calculate new pen location and delta */ tm.e = text->items[i].x; tm.f = text->items[i].y; fz_concat(&trm, &tm, ctm); delta.x = pen->x - trm.e; delta.y = pen->y - trm.f; if (pen->x == -1 && pen->y == -1) delta.x = delta.y = 0; dist = sqrtf(delta.x * delta.x + delta.y * delta.y); /* Add space and newlines based on pen movement */ if (dist > 0) { /* SumatraPDF: don't add spaces for large overlapping glyphs */ fz_text_span *last = &dev->cur_span; if (last->len == 0 && dev->cur_line.len > 0) last = &dev->cur_line.spans[dev->cur_line.len - 1]; ndelta.x = delta.x / dist; ndelta.y = delta.y / dist; dot = ndelta.x * ndir.x + ndelta.y * ndir.y; /* SumatraPDF: don't merge multiple lines into one */ if (dist > size * LINE_DIST && hypotf(delta.y * ndir.x, delta.x * ndir.y) > size * 0.5f) { fz_flush_text_line(ctx, dev, style); dev->lastchar = ' '; } else /* SumatraPDF: use 0.95f instead of 0.9995f for slightly better results */ if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST && dist < size * SPACE_MAX_DIST) { if (dev->lastchar != ' ' && /* SumatraPDF: don't add spaces before spaces or for large overlapping glyphs */ text->items[i].ucs != ' ' && !fz_maps_into_rect(trm, last->text[last->len - 1].bbox)) { fz_rect spacerect; spacerect.x0 = -0.2f; spacerect.y0 = descender; spacerect.x1 = 0; spacerect.y1 = ascender; fz_transform_rect(&spacerect, &trm); fz_add_text_char(ctx, dev, style, ' ', spacerect); dev->lastchar = ' '; } } else if (dist > size * LINE_DIST) { fz_flush_text_line(ctx, dev, style); dev->lastchar = ' '; } } /* Calculate bounding box and new pen position based on font metrics */ if (font->ft_face) { FT_Fixed ftadv = 0; int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; /* TODO: freetype returns broken vertical metrics */ /* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */ fz_lock(ctx, FZ_LOCK_FREETYPE); err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72); if (err) fz_warn(ctx, "freetype set character size: %s", ft_error_string(err)); FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv); adv = ftadv / 65536.0f; fz_unlock(ctx, FZ_LOCK_FREETYPE); rect.x0 = 0; rect.y0 = descender; rect.x1 = adv; rect.y1 = ascender; } /* SumatraPDF: TODO: this check might no longer be needed */ else if (text->items[i].gid < 256) { adv = font->t3widths[text->items[i].gid]; rect.x0 = 0; rect.y0 = descender; rect.x1 = adv; rect.y1 = ascender; } fz_transform_rect(&rect, &trm); /* cf. http://code.google.com/p/sumatrapdf/issues/detail?id=1839 */ if (font->ft_face) { fz_rect bbox; fz_bound_glyph(ctx, font, text->items[i].gid, &trm, &bbox); rect.y0 = fz_min(rect.y0, bbox.y0); rect.y1 = fz_max(rect.y1, bbox.y1); } pen->x = trm.e + dir.x * adv; pen->y = trm.f + dir.y * adv; /* Check for one glyph to many char mapping */ for (j = i + 1; j < text->len; j++) if (text->items[j].gid >= 0) break; multi = j - i; if (multi == 1) { fz_add_text_char(ctx, dev, style, text->items[i].ucs, rect); } else { for (j = 0; j < multi; j++) { fz_rect part = fz_split_bbox(rect, j, multi); fz_add_text_char(ctx, dev, style, text->items[i + j].ucs, part); } i += j - 1; } dev->lastchar = text->items[i].ucs; } }
static void svg_dev_text(fz_context *ctx, svg_device *sdev, const fz_matrix *ctm, fz_text *text) { fz_output *out = sdev->out; int i; fz_matrix inverse; fz_matrix local_trm; float size; int start, is_wspace, was_wspace; fz_text_span *span; for (span = text->head; text; span = span->next) { /* Rely on the fact that trm.{e,f} == 0 */ size = fz_matrix_expansion(&span->trm); local_trm.a = span->trm.a / size; local_trm.b = span->trm.b / size; local_trm.c = -span->trm.c / size; local_trm.d = -span->trm.d / size; local_trm.e = 0; local_trm.f = 0; fz_invert_matrix(&inverse, &local_trm); fz_concat(&local_trm, &local_trm, ctm); fz_printf(ctx, out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"", local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f); fz_printf(ctx, out, " font-size=\"%g\"", size); fz_printf(ctx, out, " font-family=\"%s\"", span->font->name); /* Leading (and repeated) whitespace presents a problem for SVG * text, so elide it here. */ for (start=0; start < span->len; start++) { fz_text_item *it = &span->items[start]; if (!is_xml_wspace(it->ucs)) break; } fz_printf(ctx, out, " x="); was_wspace = 0; for (i=start; i < span->len; i++) { fz_text_item *it = &span->items[i]; fz_point p; is_wspace = is_xml_wspace(it->ucs); if (is_wspace && was_wspace) continue; was_wspace = is_wspace; p.x = it->x; p.y = it->y; fz_transform_point(&p, &inverse); fz_printf(ctx, out, "%c%g", i == start ? '\"' : ' ', p.x); } fz_printf(ctx, out, "\" y="); was_wspace = 0; for (i=start; i < span->len; i++) { fz_text_item *it = &span->items[i]; fz_point p; is_wspace = is_xml_wspace(it->ucs); if (is_wspace && was_wspace) continue; was_wspace = is_wspace; p.x = it->x; p.y = it->y; fz_transform_point(&p, &inverse); fz_printf(ctx, out, "%c%g", i == start ? '\"' : ' ', p.y); } fz_printf(ctx, out, "\">\n"); was_wspace = 0; for (i=start; i < span->len; i++) { fz_text_item *it = &span->items[i]; int c = it->ucs; is_wspace = is_xml_wspace(c); if (is_wspace && was_wspace) continue; was_wspace = is_wspace; if (c >= 32 && c <= 127 && c != '<' && c != '&') fz_printf(ctx, out, "%c", c); else fz_printf(ctx, out, "&#x%04x;", c); } fz_printf(ctx, out, "\n</text>\n"); } }
static fz_text_line * push_span(fz_context *ctx, fz_text_device *tdev, fz_text_span *span, int new_line, float distance) { fz_text_line *line; fz_text_block *block; fz_text_page *page = tdev->page; int prev_not_text = 0; if (page->len == 0 || page->blocks[page->len-1].type != FZ_PAGE_BLOCK_TEXT) prev_not_text = 1; if (new_line || prev_not_text) { float size = fz_matrix_expansion(&span->transform); /* So, a new line. Part of the same block or not? */ if (distance == 0 || distance > size * 1.5 || distance < -size * PARAGRAPH_DIST || page->len == 0 || prev_not_text) { /* New block */ if (page->len == page->cap) { int newcap = (page->cap ? page->cap*2 : 4); page->blocks = fz_resize_array(ctx, page->blocks, newcap, sizeof(*page->blocks)); page->cap = newcap; } block = fz_malloc_struct(ctx, fz_text_block); page->blocks[page->len].type = FZ_PAGE_BLOCK_TEXT; page->blocks[page->len].u.text = block; block->cap = 0; block->len = 0; block->lines = 0; block->bbox = fz_empty_rect; page->len++; distance = 0; } /* New line */ block = page->blocks[page->len-1].u.text; if (block->len == block->cap) { int newcap = (block->cap ? block->cap*2 : 4); block->lines = fz_resize_array(ctx, block->lines, newcap, sizeof(*block->lines)); block->cap = newcap; } block->lines[block->len].first_span = NULL; block->lines[block->len].last_span = NULL; block->lines[block->len].distance = distance; block->lines[block->len].bbox = fz_empty_rect; block->len++; } /* Find last line and append to it */ block = page->blocks[page->len-1].u.text; line = &block->lines[block->len-1]; fz_union_rect(&block->lines[block->len-1].bbox, &span->bbox); fz_union_rect(&block->bbox, &span->bbox); span->base_offset = (new_line ? 0 : distance); if (!line->first_span) { line->first_span = line->last_span = span; span->next = NULL; } else { line->last_span->next = span; line->last_span = span; } return line; }
static void fz_text_extract_span(fz_context *ctx, fz_text_span **last, fz_text *text, fz_matrix ctm, fz_point *pen) { fz_font *font = text->font; FT_Face face = font->ft_face; fz_matrix tm = text->trm; fz_matrix trm; float size; float adv; fz_rect rect; fz_point dir, ndir; fz_point delta, ndelta; float dist, dot; float ascender = 1; float descender = 0; int multi; int i, err; if (text->len == 0) return; fz_lock(ctx, FZ_LOCK_FREETYPE); if (font->ft_face) { err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72); if (err) fz_warn(ctx, "freetype set character size: %s", ft_error_string(err)); ascender = (float)face->ascender / face->units_per_EM; descender = (float)face->descender / face->units_per_EM; } rect = fz_empty_rect; if (text->wmode == 0) { dir.x = 1; dir.y = 0; } else { dir.x = 0; dir.y = 1; } tm.e = 0; tm.f = 0; trm = fz_concat(tm, ctm); dir = fz_transform_vector(trm, dir); dist = sqrtf(dir.x * dir.x + dir.y * dir.y); ndir.x = dir.x / dist; ndir.y = dir.y / dist; size = fz_matrix_expansion(trm); multi = 1; for (i = 0; i < text->len; i++) { if (text->items[i].gid < 0) { fz_add_text_char(ctx, last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect)); multi ++; fz_divide_text_chars(last, multi, fz_round_rect(rect)); continue; } multi = 1; /* Calculate new pen location and delta */ tm.e = text->items[i].x; tm.f = text->items[i].y; trm = fz_concat(tm, ctm); delta.x = pen->x - trm.e; delta.y = pen->y - trm.f; if (pen->x == -1 && pen->y == -1) delta.x = delta.y = 0; dist = sqrtf(delta.x * delta.x + delta.y * delta.y); /* Add space and newlines based on pen movement */ if (dist > 0) { ndelta.x = delta.x / dist; ndelta.y = delta.y / dist; dot = ndelta.x * ndir.x + ndelta.y * ndir.y; if (dist > size * LINE_DIST) { fz_add_text_newline(ctx, last, font, size, text->wmode); } else if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST) { if ((*last)->len > 0 && (*last)->text[(*last)->len - 1].c != ' ') { fz_rect spacerect; spacerect.x0 = -0.2f; spacerect.y0 = 0; spacerect.x1 = 0; spacerect.y1 = 1; spacerect = fz_transform_rect(trm, spacerect); fz_add_text_char(ctx, last, font, size, text->wmode, ' ', fz_round_rect(spacerect)); } } } /* Calculate bounding box and new pen position based on font metrics */ if (font->ft_face) { FT_Fixed ftadv = 0; int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; /* TODO: freetype returns broken vertical metrics */ /* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */ FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv); adv = ftadv / 65536.0f; rect.x0 = 0; rect.y0 = descender; rect.x1 = adv; rect.y1 = ascender; } else { adv = font->t3widths[text->items[i].gid]; rect.x0 = 0; rect.y0 = descender; rect.x1 = adv; rect.y1 = ascender; } rect = fz_transform_rect(trm, rect); pen->x = trm.e + dir.x * adv; pen->y = trm.f + dir.y * adv; fz_add_text_char(ctx, last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect)); } fz_unlock(ctx, FZ_LOCK_FREETYPE); }
/* Render a glyph and return a bitmap. If the glyph is too large to fit the cache we have two choices: 1) Return NULL so the caller can draw the glyph using an outline. Only supported for freetype fonts. 2) Render a clipped glyph by using the scissor rectangle. Only supported for type 3 fonts. This must not be inserted into the cache. */ fz_pixmap * fz_render_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *ctm, fz_colorspace *model, fz_irect scissor) { fz_glyph_cache *cache; fz_glyph_key key; fz_pixmap *val; float size = fz_matrix_expansion(ctm); int do_cache, locked, caching; fz_matrix local_ctm = *ctm; fz_glyph_cache_entry *entry; unsigned hash; fz_var(locked); fz_var(caching); if (size <= MAX_GLYPH_SIZE) { scissor = fz_infinite_irect; do_cache = 1; } else { /* SumatraPDF: don't break clipping by larger glyphs */ if (font->ft_face && size > 3000) return NULL; do_cache = 0; } cache = ctx->glyph_cache; memset(&key, 0, sizeof key); key.font = font; key.gid = gid; key.a = local_ctm.a * 65536; key.b = local_ctm.b * 65536; key.c = local_ctm.c * 65536; key.d = local_ctm.d * 65536; key.e = (local_ctm.e - floorf(local_ctm.e)) * 256; key.f = (local_ctm.f - floorf(local_ctm.f)) * 256; key.aa = fz_aa_level(ctx); local_ctm.e = floorf(local_ctm.e) + key.e / 256.0f; local_ctm.f = floorf(local_ctm.f) + key.f / 256.0f; fz_lock(ctx, FZ_LOCK_GLYPHCACHE); hash = do_hash((unsigned char *)&key, sizeof(key)) % GLYPH_HASH_LEN; entry = cache->entry[hash]; while (entry) { if (memcmp(&entry->key, &key, sizeof(key)) == 0) { move_to_front(cache, entry); val = fz_keep_pixmap(ctx, entry->val); fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); return val; } entry = entry->bucket_next; } locked = 1; caching = 0; fz_try(ctx) { if (font->ft_face) { val = fz_render_ft_glyph(ctx, font, gid, &local_ctm, key.aa); } else if (font->t3procs) { /* We drop the glyphcache here, and execute the t3 * glyph code. The danger here is that some other * thread will come along, and want the same glyph * too. If it does, we may both end up rendering * pixmaps. We cope with this later on, by ensuring * that only one gets inserted into the cache. If * we insert ours to find one already there, we * abandon ours, and use the one there already. */ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); locked = 0; val = fz_render_t3_glyph(ctx, font, gid, &local_ctm, model, scissor); fz_lock(ctx, FZ_LOCK_GLYPHCACHE); locked = 1; } else { fz_warn(ctx, "assert: uninitialized font structure"); val = NULL; } if (val && do_cache) { if (val->w < MAX_GLYPH_SIZE && val->h < MAX_GLYPH_SIZE) { /* If we throw an exception whilst caching, * just ignore the exception and carry on. */ caching = 1; if (!font->ft_face) { /* We had to unlock. Someone else might * have rendered in the meantime */ entry = cache->entry[hash]; while (entry) { if (memcmp(&entry->key, &key, sizeof(key)) == 0) { fz_drop_pixmap(ctx, val); move_to_front(cache, entry); val = fz_keep_pixmap(ctx, entry->val); fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); return val; } entry = entry->bucket_next; } } entry = fz_malloc_struct(ctx, fz_glyph_cache_entry); entry->key = key; entry->hash = hash; entry->bucket_next = cache->entry[hash]; if (entry->bucket_next) entry->bucket_next->bucket_prev = entry; cache->entry[hash] = entry; entry->val = fz_keep_pixmap(ctx, val); fz_keep_font(ctx, key.font); entry->lru_next = cache->lru_head; if (entry->lru_next) entry->lru_next->lru_prev = entry; else cache->lru_tail = entry; cache->lru_head = entry; cache->total += val->w * val->h; while (cache->total > MAX_CACHE_SIZE) { drop_glyph_cache_entry(ctx, cache->lru_tail); } } } } fz_always(ctx) { if (locked) fz_unlock(ctx, FZ_LOCK_GLYPHCACHE); } fz_catch(ctx) { if (caching) fz_warn(ctx, "cannot encache glyph; continuing"); else fz_rethrow(ctx); } return val; }
static void fz_add_text_char_imp(fz_context *ctx, fz_text_device *dev, fz_text_style *style, int c, fz_matrix *trm, float adv, int wmode) { int can_append = 1; int add_space = 0; fz_point dir, ndir, p, q, r; float size; fz_point delta; float spacing = 0; float base_offset = 0; if (wmode == 0) { dir.x = 1; dir.y = 0; } else { dir.x = 0; dir.y = -1; } fz_transform_vector(&dir, trm); ndir = dir; fz_normalize_vector(&ndir); /* dir = direction vector for motion. ndir = normalised(dir) */ size = fz_matrix_expansion(trm); /* We need to identify where glyphs 'start' (p) and 'stop' (q). * Each glyph holds it's 'start' position, and the next glyph in the * span (or span->max if there is no next glyph) holds it's 'end' * position. * * For both horizontal and vertical motion, trm->{e,f} gives the * bottom left corner of the glyph. * * In horizontal mode: * + p is bottom left. * + q is the bottom right * In vertical mode: * + p is top left (where it advanced from) * + q is bottom left */ if (wmode == 0) { p.x = trm->e; p.y = trm->f; q.x = trm->e + adv * dir.x; q.y = trm->f + adv * dir.y; } else { p.x = trm->e - adv * dir.x; p.y = trm->f - adv * dir.y; q.x = trm->e; q.y = trm->f; } if (dev->cur_span == NULL || trm->a != dev->cur_span->transform.a || trm->b != dev->cur_span->transform.b || trm->c != dev->cur_span->transform.c || trm->d != dev->cur_span->transform.d || dev->cur_span->wmode != wmode) { /* If the matrix has changed, or the wmode is different (or * if we don't have a span at all), then we can't append. */ #ifdef DEBUG_SPANS printf("Transform/WMode changed\n"); #endif can_append = 0; } else { /* Calculate how far we've moved since the end of the current * span. */ delta.x = p.x - dev->cur_span->max.x; delta.y = p.y - dev->cur_span->max.y; /* The transform has not changed, so we know we're in the same * direction. Calculate 2 distances; how far off the previous * baseline we are, together with how far along the baseline * we are from the expected position. */ spacing = ndir.x * delta.x + ndir.y * delta.y; base_offset = -ndir.y * delta.x + ndir.x * delta.y; spacing /= size * SPACE_DIST; spacing = fabsf(spacing); if (fabsf(base_offset) < size * 0.1) { /* Only a small amount off the baseline - we'll take this */ if (spacing < 1.0) { /* Motion is in line, and small. */ } else if (spacing >= 1 && spacing < (SPACE_MAX_DIST/SPACE_DIST)) { /* Motion is in line, but large enough * to warrant us adding a space */ if (dev->lastchar != ' ' && wmode == 0) add_space = 1; } else { /* Motion is in line, but too large - split to a new span */ can_append = 0; } } else { can_append = 0; #ifdef DEBUG_SPANS spacing = 0; #endif } } #ifdef DEBUG_SPANS printf("%c%c append=%d space=%d size=%g spacing=%g base_offset=%g\n", dev->lastchar, c, can_append, add_space, size, spacing, base_offset); #endif if (can_append == 0) { /* Start a new span */ add_span_to_soup(ctx, dev->spans, dev->cur_span); dev->cur_span = NULL; dev->cur_span = fz_new_text_span(ctx, &p, wmode, trm); dev->cur_span->spacing = 0; } if (add_space) { r.x = - 0.2f; r.y = 0; fz_transform_point(&r, trm); add_char_to_span(ctx, dev->cur_span, ' ', &p, &r, style); } add_char_to_span(ctx, dev->cur_span, c, &p, &q, style); }
static void strain_soup(fz_context *ctx, fz_text_device *tdev) { span_soup *soup = tdev->spans; fz_text_line *last_line = NULL; fz_text_span *last_span = NULL; int span_num; if (soup == NULL) return; /* Really dumb implementation to match what we had before */ for (span_num=0; span_num < soup->len; span_num++) { fz_text_span *span = soup->spans[span_num]; int new_line = 1; float distance = 0; float spacing = 0; soup->spans[span_num] = NULL; if (last_span) { /* If we have a last_span, we must have a last_line */ /* Do span and last_line share the same baseline? */ fz_point p, q, perp_r; float dot; float size = fz_matrix_expansion(&span->transform); #ifdef DEBUG_SPANS { printf("Comparing: \""); dump_span(last_span); printf("\" and \""); dump_span(span); printf("\"\n"); } #endif p.x = last_line->first_span->max.x - last_line->first_span->min.x; p.y = last_line->first_span->max.y - last_line->first_span->min.y; fz_normalize_vector(&p); q.x = span->max.x - span->min.x; q.y = span->max.y - span->min.y; fz_normalize_vector(&q); #ifdef DEBUG_SPANS printf("last_span=%g %g -> %g %g = %g %g\n", last_span->min.x, last_span->min.y, last_span->max.x, last_span->max.y, p.x, p.y); printf("span =%g %g -> %g %g = %g %g\n", span->min.x, span->min.y, span->max.x, span->max.y, q.x, q.y); #endif perp_r.y = last_line->first_span->min.x - span->min.x; perp_r.x = -(last_line->first_span->min.y - span->min.y); /* Check if p and q are parallel. If so, then this * line is parallel with the last one. */ dot = p.x * q.x + p.y * q.y; if (fabsf(dot) > 0.9995) { /* If we take the dot product of normalised(p) and * perp(r), we get the perpendicular distance from * one line to the next (assuming they are parallel). */ distance = p.x * perp_r.x + p.y * perp_r.y; /* We allow 'small' distances of baseline changes * to cope with super/subscript. FIXME: We should * gather subscript/superscript information here. */ new_line = (fabsf(distance) > size * LINE_DIST); } else { new_line = 1; distance = 0; } if (!new_line) { fz_point delta; delta.x = span->min.x - last_span->max.x; delta.y = span->min.y - last_span->max.y; spacing = (p.x * delta.x + p.y * delta.y); spacing = fabsf(spacing); /* Only allow changes in baseline (subscript/superscript etc) * when the spacing is small. */ if (spacing * fabsf(distance) > size * LINE_DIST && fabsf(distance) > size * 0.1f) { new_line = 1; distance = 0; spacing = 0; } else { spacing /= size * SPACE_DIST; /* Apply the same logic here as when we're adding chars to build spans. */ if (spacing >= 1 && spacing < (SPACE_MAX_DIST/SPACE_DIST)) spacing = 1; } } #ifdef DEBUG_SPANS printf("dot=%g new_line=%d distance=%g size=%g spacing=%g\n", dot, new_line, distance, size, spacing); #endif } span->spacing = spacing; last_line = push_span(ctx, tdev, span, new_line, distance); last_span = span; } }
static void fz_draw_clip_path(fz_context *ctx, void *user, fz_path *path, fz_rect *rect, int even_odd, fz_matrix ctm) { fz_draw_device *dev = user; fz_colorspace *model = dev->dest->colorspace; float expansion = fz_matrix_expansion(ctm); float flatness = 0.3f / expansion; fz_pixmap *mask, *dest, *shape; fz_bbox bbox; if (dev->top == dev->stack_max) fz_grow_stack(dev); fz_reset_gel(dev->gel, dev->scissor); fz_flatten_fill_path(ctx, dev->gel, path, ctm, flatness); fz_sort_gel(dev->gel); bbox = fz_bound_gel(dev->gel); bbox = fz_intersect_bbox(bbox, dev->scissor); if (rect) bbox = fz_intersect_bbox(bbox, fz_round_rect(*rect)); if (fz_is_empty_rect(bbox) || fz_is_rect_gel(dev->gel)) { dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = NULL; dev->stack[dev->top].dest = NULL; dev->stack[dev->top].shape = dev->shape; dev->stack[dev->top].blendmode = dev->blendmode; dev->scissor = bbox; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top, "Clip (rectangular) begin\n"); #endif dev->top++; return; } mask = fz_new_pixmap_with_rect(ctx, NULL, bbox); fz_clear_pixmap(mask); dest = fz_new_pixmap_with_rect(ctx, model, bbox); /* FIXME: See note #1 */ fz_clear_pixmap(dest); if (dev->shape) { shape = fz_new_pixmap_with_rect(ctx, NULL, bbox); fz_clear_pixmap(shape); } else shape = NULL; fz_scan_convert(dev->gel, even_odd, bbox, mask, NULL); dev->stack[dev->top].scissor = dev->scissor; dev->stack[dev->top].mask = mask; dev->stack[dev->top].dest = dev->dest; dev->stack[dev->top].shape = dev->shape; /* FIXME: See note #1 */ dev->stack[dev->top].blendmode = dev->blendmode | FZ_BLEND_ISOLATED; dev->scissor = bbox; dev->dest = dest; dev->shape = shape; #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top, "Clip (non-rectangular) begin\n"); #endif dev->top++; }
static void fz_text_extract_span(fz_text_span **last, fz_text *text, fz_matrix ctm, fz_point *pen) { // fz_font *font = text->font; // FT_Face face = font->ft_face; fz_matrix tm = text->trm; fz_matrix trm; float size; float adv = 0; fz_rect rect; fz_point dir, ndir; fz_point delta, ndelta; float dist, dot; float ascender = 1; float descender = 0; int multi; int i; if (text->len == 0) return; rect = fz_empty_rect; /* SumatraPDF: TODO: make this depend on the per-glyph displacement vector */ if (text->wmode == 0) { dir.x = 1; dir.y = 0; } else { dir.x = 0; dir.y = 1; } tm.e = 0; tm.f = 0; trm = fz_concat(tm, ctm); dir = fz_transform_vector(trm, dir); dist = sqrtf(dir.x * dir.x + dir.y * dir.y); ndir.x = dir.x / dist; ndir.y = dir.y / dist; size = fz_matrix_expansion(trm); multi = 1; for (i = 0; i < text->len; i++) { if (text->items[i].gid < 0) { fz_add_text_char(last, size, text->wmode, text->items[i].ucs, fz_round_rect(rect)); multi ++; fz_divide_text_chars(last, multi, fz_round_rect(rect)); continue; } multi = 1; /* Calculate new pen location and delta */ tm.e = text->items[i].x; tm.f = text->items[i].y; trm = fz_concat(tm, ctm); delta.x = pen->x - trm.e; delta.y = pen->y - trm.f; if (pen->x == -1 && pen->y == -1) delta.x = delta.y = 0; dist = sqrtf(delta.x * delta.x + delta.y * delta.y); /* Add space and newlines based on pen movement */ if (dist > 0) { ndelta.x = delta.x / dist; ndelta.y = delta.y / dist; dot = ndelta.x * ndir.x + ndelta.y * ndir.y; if (dist > size * LINE_DIST) { fz_add_text_newline(last, size, text->wmode); } else if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST) { if ((*last)->len > 0 && (*last)->text[(*last)->len - 1].c != ' ') { fz_rect spacerect; spacerect.x0 = -0.2f; spacerect.y0 = 0; spacerect.x1 = 0; spacerect.y1 = 1; spacerect = fz_transform_rect(trm, spacerect); fz_add_text_char(last, size, text->wmode, ' ', fz_round_rect(spacerect)); } } } /* Calculate bounding box and new pen position based on font metrics */ //if (font->ft_face) //{ // FT_Fixed ftadv = 0; // int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; // /* TODO: freetype returns broken vertical metrics */ // /* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */ // FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv); // adv = ftadv / 65536.0f; // rect.x0 = 0; // rect.y0 = descender; // rect.x1 = adv; // rect.y1 = ascender; //} //else { // adv = font->t3widths[text->items[i].gid]; rect.x0 = 0; rect.y0 = descender; rect.x1 = adv; rect.y1 = ascender; } rect = fz_transform_rect(trm, rect); pen->x = trm.e + dir.x * adv; pen->y = trm.f + dir.y * adv; fz_add_text_char(last, size, text->wmode, text->items[i].ucs, fz_round_rect(rect)); } }
void fz_print_text_page_html(fz_context *ctx, fz_output *out, fz_text_page *page) { int block_n, line_n, ch_n; fz_text_style *style = NULL; fz_text_line *line; fz_text_span *span; void *last_region = NULL; fz_printf(out, "<div class=\"page\">\n"); for (block_n = 0; block_n < page->len; block_n++) { switch (page->blocks[block_n].type) { case FZ_PAGE_BLOCK_TEXT: { fz_text_block * block = page->blocks[block_n].u.text; fz_printf(out, "<div class=\"block\"><p>\n"); for (line_n = 0; line_n < block->len; line_n++) { int lastcol=-1; line = &block->lines[line_n]; style = NULL; if (line->region != last_region) { if (last_region) fz_printf(out, "</div>"); fz_printf(out, "<div class=\"metaline\">"); last_region = line->region; } fz_printf(out, "<div class=\"line\""); #ifdef DEBUG_INTERNALS if (line->region) fz_printf(out, " region=\"%x\"", line->region); #endif fz_printf(out, ">"); for (span = line->first_span; span; span = span->next) { float size = fz_matrix_expansion(&span->transform); float base_offset = span->base_offset / size; if (lastcol != span->column) { if (lastcol >= 0) { fz_printf(out, "</div>"); } /* If we skipped any columns then output some spacer spans */ while (lastcol < span->column-1) { fz_printf(out, "<div class=\"cell\"></div>"); lastcol++; } lastcol++; /* Now output the span to contain this entire column */ fz_printf(out, "<div class=\"cell\" style=\""); { fz_text_span *sn; for (sn = span->next; sn; sn = sn->next) { if (sn->column != lastcol) break; } fz_printf(out, "width:%g%%;align:%s", span->column_width, (span->align == 0 ? "left" : (span->align == 1 ? "center" : "right"))); } if (span->indent > 1) fz_printf(out, ";padding-left:1em;text-indent:-1em"); if (span->indent < -1) fz_printf(out, ";text-indent:1em"); fz_printf(out, "\">"); } #ifdef DEBUG_INTERNALS fz_printf(out, "<span class=\"internal_span\""); if (span->column) fz_printf(out, " col=\"%x\"", span->column); fz_printf(out, ">"); #endif if (span->spacing >= 1) fz_printf(out, " "); if (base_offset > SUBSCRIPT_OFFSET) fz_printf(out, "<sub>"); else if (base_offset < SUPERSCRIPT_OFFSET) fz_printf(out, "<sup>"); for (ch_n = 0; ch_n < span->len; ch_n++) { fz_text_char *ch = &span->text[ch_n]; if (style != ch->style) { if (style) fz_print_style_end(out, style); fz_print_style_begin(out, ch->style); style = ch->style; } if (ch->c == '<') fz_printf(out, "<"); else if (ch->c == '>') fz_printf(out, ">"); else if (ch->c == '&') fz_printf(out, "&"); else if (ch->c >= 32 && ch->c <= 127) fz_printf(out, "%c", ch->c); else fz_printf(out, "&#x%x;", ch->c); } if (style) { fz_print_style_end(out, style); style = NULL; } if (base_offset > SUBSCRIPT_OFFSET) fz_printf(out, "</sub>"); else if (base_offset < SUPERSCRIPT_OFFSET) fz_printf(out, "</sup>"); #ifdef DEBUG_INTERNALS fz_printf(out, "</span>"); #endif } /* Close our floating span */ fz_printf(out, "</div>"); /* Close the line */ fz_printf(out, "</div>"); fz_printf(out, "\n"); } /* Close the metaline */ fz_printf(out, "</div>"); last_region = NULL; fz_printf(out, "</p></div>\n"); break; } case FZ_PAGE_BLOCK_IMAGE: { fz_image_block *image = page->blocks[block_n].u.image; fz_printf(out, "<img width=%d height=%d src=\"data:", image->image->w, image->image->h); switch (image->image->buffer == NULL ? FZ_IMAGE_JPX : image->image->buffer->params.type) { case FZ_IMAGE_JPEG: fz_printf(out, "image/jpeg;base64,"); send_data_base64(out, image->image->buffer->buffer); break; case FZ_IMAGE_PNG: fz_printf(out, "image/png;base64,"); send_data_base64(out, image->image->buffer->buffer); break; default: { fz_buffer *buf = fz_image_as_png(ctx, image->image, image->image->w, image->image->h); fz_printf(out, "image/png;base64,"); send_data_base64(out, buf); fz_drop_buffer(ctx, buf); break; } } fz_printf(out, "\">\n"); break; } } } fz_printf(out, "</div>\n"); }