Exemple #1
0
int
gs_matrix_invert_to_double(const gs_matrix * pm, gs_matrix_double * pmr)
{				/* We have to be careful about fetch/store order, */
    /* because pm might be the same as pmr. */
    if (is_xxyy(pm)) {
        if (is_fzero(pm->xx) || is_fzero(pm->yy))
            return_error(gs_error_undefinedresult);
        pmr->tx = -(pmr->xx = 1.0 / pm->xx) * pm->tx;
        pmr->xy = 0.0;
        pmr->yx = 0.0;
        pmr->ty = -(pmr->yy = 1.0 / pm->yy) * pm->ty;
    } else {
        double mxx = pm->xx, myy = pm->yy, mxy = pm->xy, myx = pm->yx;
        double mtx = pm->tx, mty = pm->ty;
        double det = (mxx * myy) - (mxy * myx);

        /*
         * We are doing the math as floats instead of doubles to reproduce
         * the results in page 1 of CET 10-09.ps
         */
        if (det == 0)
            return_error(gs_error_undefinedresult);
        pmr->xx = myy / det;
        pmr->xy = -mxy / det;
        pmr->yx = -myx / det;
        pmr->yy = mxx / det;
        pmr->tx = (((mty * myx) - (mtx * myy))) / det;
        pmr->ty = (((mtx * mxy) - (mty * mxx))) / det;
    }
    return 0;
}
Exemple #2
0
/* Since this is used heavily, we check for shortcuts. */
int
gs_matrix_multiply(const gs_matrix * pm1, const gs_matrix * pm2, gs_matrix * pmr)
{
    double xx1 = pm1->xx, yy1 = pm1->yy;
    double tx1 = pm1->tx, ty1 = pm1->ty;
    double xx2 = pm2->xx, yy2 = pm2->yy;
    double xy2 = pm2->xy, yx2 = pm2->yx;

    if (is_xxyy(pm1)) {
        pmr->tx = tx1 * xx2 + pm2->tx;
        pmr->ty = ty1 * yy2 + pm2->ty;
        if (is_fzero(xy2))
            pmr->xy = 0;
        else
            pmr->xy = xx1 * xy2,
                pmr->ty += tx1 * xy2;
        pmr->xx = xx1 * xx2;
        if (is_fzero(yx2))
            pmr->yx = 0;
        else
            pmr->yx = yy1 * yx2,
                pmr->tx += ty1 * yx2;
        pmr->yy = yy1 * yy2;
    } else {
        double xy1 = pm1->xy, yx1 = pm1->yx;

        pmr->xx = xx1 * xx2 + xy1 * yx2;
        pmr->xy = xx1 * xy2 + xy1 * yy2;
        pmr->yy = yx1 * xy2 + yy1 * yy2;
        pmr->yx = yx1 * xx2 + yy1 * yx2;
        pmr->tx = tx1 * xx2 + ty1 * yx2 + pm2->tx;
        pmr->ty = tx1 * xy2 + ty1 * yy2 + pm2->ty;
    }
    return 0;
}
Exemple #3
0
/* Return gs_error_undefinedresult if the matrix is not invertible. */
int
gs_point_transform_inverse(floatp x, floatp y, const gs_matrix * pmat,
                           gs_point * ppt)
{
    if (is_xxyy(pmat)) {
        if (is_fzero(pmat->xx) || is_fzero(pmat->yy))
            return_error(gs_error_undefinedresult);
        ppt->x = (x - pmat->tx) / pmat->xx;
        ppt->y = (y - pmat->ty) / pmat->yy;
        return 0;
    } else if (is_xyyx(pmat)) {
        if (is_fzero(pmat->xy) || is_fzero(pmat->yx))
            return_error(gs_error_undefinedresult);
        ppt->x = (y - pmat->ty) / pmat->xy;
        ppt->y = (x - pmat->tx) / pmat->yx;
        return 0;
    } else {			/* There are faster ways to do this, */
        /* but we won't implement one unless we have to. */
        gs_matrix imat;
        int code = gs_matrix_invert(pmat, &imat);

        if (code < 0)
            return code;
        return gs_point_transform(x, y, &imat, ppt);
    }
}
/*
 * Determine the flatness for rendering a character in an outline font.
 * This may be less than the flatness in the imager state.
 * The second argument is the default scaling for the font: 0.001 for
 * Type 1 fonts, 1.0 for TrueType fonts.
 */
double
gs_char_flatness(const gs_imager_state *pis, floatp default_scale)
{
    /*
     * Set the flatness to a value that is likely to produce reasonably
     * good-looking curves, regardless of its current value in the
     * graphics state.  If the character is very small, set the flatness
     * to zero, which will produce very accurate curves.
     */
    double cxx = fabs(pis->ctm.xx), cyy = fabs(pis->ctm.yy);

    if (is_fzero(cxx) || (cyy < cxx && !is_fzero(cyy)))
	cxx = cyy;
    if (!is_xxyy(&pis->ctm)) {
	double cxy = fabs(pis->ctm.xy), cyx = fabs(pis->ctm.yx);

	if (is_fzero(cxx) || (cxy < cxx && !is_fzero(cxy)))
	    cxx = cxy;
	if (is_fzero(cxx) || (cyx < cxx && !is_fzero(cyx)))
	    cxx = cyx;
    }
    /* Correct for the default scaling. */
    cxx *= 0.001 / default_scale;
    /* Don't let the flatness be worse than the default. */
    if (cxx > pis->flatness)
	cxx = pis->flatness;
    /* If the character is tiny, force accurate curves. */
    if (cxx < 0.2)
	cxx = 0;
    return cxx;
}
Exemple #5
0
/* Check whether color is a shading with BBox. */
int
gx_dc_pattern2_is_rectangular_cell(const gx_device_color * pdevc, gx_device * pdev, gs_fixed_rect *rect)
{
    if (gx_dc_is_pattern2_color(pdevc) && gx_dc_pattern2_color_has_bbox(pdevc) &&
            (*dev_proc(pdev, dev_spec_op))(pdev, gxdso_pattern_shading_area, NULL, 0) == 0) {
        gs_pattern2_instance_t *pinst = (gs_pattern2_instance_t *)pdevc->ccolor.pattern;
        const gs_shading_t *psh = pinst->templat.Shading;
        gs_fixed_point p, q;

        if (is_xxyy(&ctm_only(pinst->saved)))
            if (psh->params.have_BBox) {
                int code = gs_point_transform2fixed(&pinst->saved->ctm,
                            psh->params.BBox.p.x, psh->params.BBox.p.y, &p);
                if (code < 0)
                    return code;
                code = gs_point_transform2fixed(&pinst->saved->ctm,
                            psh->params.BBox.q.x, psh->params.BBox.q.y, &q);
                if (code < 0)
                    return code;
                if (p.x > q.x) {
                    p.x ^= q.x; q.x ^= p.x; p.x ^= q.x;
                }
                if (p.y > q.y) {
                    p.y ^= q.y; q.y ^= p.y; p.y ^= q.y;
                }
                rect->p = p;
                rect->q = q;
                return 1;
            }
    }
    return 0;
}
Exemple #6
0
/* Invert a matrix.  Return gs_error_undefinedresult if not invertible. */
int
gs_matrix_invert(const gs_matrix *pm, gs_matrix *pmr)
{	/* We have to be careful about fetch/store order, */
	/* because pm might be the same as pmr. */
	if ( is_xxyy(pm) )
	{	if ( is_fzero(pm->xx) || is_fzero(pm->yy) )
		  return_error(gs_error_undefinedresult);
		pmr->tx = - (pmr->xx = 1.0 / pm->xx) * pm->tx;
		pmr->xy = 0.0;
		pmr->yx = 0.0;
		pmr->ty = - (pmr->yy = 1.0 / pm->yy) * pm->ty;
	}
	else
	{	double det = pm->xx * pm->yy - pm->xy * pm->yx;
		double mxx = pm->xx, mtx = pm->tx;
		if ( det == 0 )
		  return_error(gs_error_undefinedresult);
		pmr->xx = pm->yy / det;
		pmr->xy = - pm->xy / det;
		pmr->yx = - pm->yx / det;
		pmr->yy = mxx / det;	/* xx is already changed */
		pmr->tx = - (mtx * pmr->xx + pm->ty * pmr->yx);
		pmr->ty = - (mtx * pmr->xy + pm->ty * pmr->yy);	/* tx ditto */
	}
	return 0;
}
Exemple #7
0
/* from Ghostscript */
int
matrix_invert(const MATRIX *pm, MATRIX *pmr)
{     /* We have to be careful about fetch/store order, */
      /* because pm might be the same as pmr. */
      if ( is_xxyy(pm) )
      {       if ( is_fzero(pm->xx) || is_fzero(pm->yy) )
                return -1 ;
              pmr->tx = (float)(- (pmr->xx = (float)(1.0 / pm->xx)) * pm->tx);
              pmr->xy = 0.0;
              pmr->yx = 0.0;
              pmr->ty = (float)(- (pmr->yy = (float)(1.0 / pm->yy)) * pm->ty);
      }
      else
      {       double det = pm->xx * pm->yy - pm->xy * pm->yx;
              double mxx = pm->xx, mtx = pm->tx;
              if ( det == 0 )
                return -1 ;
              pmr->xx = (float)(pm->yy / det);
              pmr->xy = (float)(- pm->xy / det);
              pmr->yx = (float)(- pm->yx / det);
              pmr->yy = (float)(mxx / det);    /* xx is already changed */
              pmr->tx = (float)(- (mtx * pmr->xx + pm->ty * pmr->yx));
              pmr->ty = (float)(- (mtx * pmr->xy + pm->ty * pmr->yy)); /* tx ditto */
      }
      return 0;
}
Exemple #8
0
/* Test whether an image has a default ImageMatrix. */
bool
gx_image_matrix_is_default(const gs_data_image_t *pid)
{
    return (is_xxyy(&pid->ImageMatrix) &&
	    pid->ImageMatrix.xx == pid->Width &&
	    pid->ImageMatrix.yy == -pid->Height &&
	    is_fzero(pid->ImageMatrix.tx) &&
	    pid->ImageMatrix.ty == pid->Height);
}
Exemple #9
0
/*
 * Compute the scale for transforming the line width and dash pattern for a
 * stroke operation, and, if necessary to handle anisotropic scaling, a full
 * transformation matrix to be inverse-applied to the path elements as well.
 * Return 0 if only scaling, 1 if a full matrix is needed.
 */
int
gdev_vector_stroke_scaling(const gx_device_vector *vdev,
                           const gs_imager_state *pis,
                           double *pscale, gs_matrix *pmat)
{
    bool set_ctm = true;
    double scale = 1;

    /*
     * If the CTM is not uniform, stroke width depends on angle.
     * We'd like to avoid resetting the CTM, so we check for uniform
     * CTMs explicitly.  Note that in PDF, unlike PostScript, it is
     * the CTM at the time of the stroke operation, not the CTM at
     * the time the path was constructed, that is used for transforming
     * the points of the path; so if we have to reset the CTM, we must
     * do it before constructing the path, and inverse-transform all
     * the coordinates.
     */
    if (is_xxyy(&pis->ctm)) {
        scale = fabs(pis->ctm.xx);
        set_ctm = fabs(pis->ctm.yy) != scale;
    } else if (is_xyyx(&pis->ctm)) {
        scale = fabs(pis->ctm.xy);
        set_ctm = fabs(pis->ctm.yx) != scale;
    } else if ((pis->ctm.xx == pis->ctm.yy && pis->ctm.xy == -pis->ctm.yx) ||
               (pis->ctm.xx == -pis->ctm.yy && pis->ctm.xy == pis->ctm.yx)
        ) {
        scale = hypot(pis->ctm.xx, pis->ctm.xy);
        set_ctm = false;
    }
    if (set_ctm) {
        /*
         * Adobe Acrobat Reader has limitations on the maximum user
         * coordinate value.  If we scale the matrix down too far, the
         * coordinates will get too big: limit the scale factor to prevent
         * this from happening.  (This does no harm for other output
         * formats.)
         */
        double
            mxx = pis->ctm.xx / vdev->scale.x,
            mxy = pis->ctm.xy / vdev->scale.y,
            myx = pis->ctm.yx / vdev->scale.x,
            myy = pis->ctm.yy / vdev->scale.y;

        scale = 0.5 * (fabs(mxx) + fabs(mxy) + fabs(myx) + fabs(myy));
        pmat->xx = mxx / scale, pmat->xy = mxy / scale;
        pmat->yx = myx / scale, pmat->yy = myy / scale;
        pmat->tx = pmat->ty = 0;
    }
    *pscale = scale;
    return (int)set_ctm;
}
Exemple #10
0
/* Invert a matrix.  Return gs_error_undefinedresult if not invertible. */
int
gs_matrix_invert(const gs_matrix * pm, gs_matrix * pmr)
{				/* We have to be careful about fetch/store order, */
    /* because pm might be the same as pmr. */
    if (is_xxyy(pm)) {
        if (is_fzero(pm->xx) || is_fzero(pm->yy))
            return_error(gs_error_undefinedresult);
        pmr->tx = -(pmr->xx = 1.0 / pm->xx) * pm->tx;
        pmr->xy = 0.0;
        pmr->yx = 0.0;
        pmr->ty = -(pmr->yy = 1.0 / pm->yy) * pm->ty;
    } else {
        float mxx = pm->xx, myy = pm->yy, mxy = pm->xy, myx = pm->yx;
        float mtx = pm->tx, mty = pm->ty;
        /* we declare det as double since on at least some computer (i.e. peeves)
           declaring it as a float results in different values for pmr depending
           on whether or not optimization is turned on.  I believe this is caused
           by the compiler keeping the det value in an internal register when
           optimization is enable.  As evidence of this if you add a debugging
           statement to print out det the optimized code acts the same as the
           unoptimized code.  declearing det as double does not change the CET 10-09.ps
           output. */
        double det = (float)(mxx * myy) - (float)(mxy * myx);

        /*
         * We are doing the math as floats instead of doubles to reproduce
         * the results in page 1 of CET 10-09.ps
         */
        if (det == 0)
            return_error(gs_error_undefinedresult);
        pmr->xx = myy / det;
        pmr->xy = -mxy / det;
        pmr->yx = -myx / det;
        pmr->yy = mxx / det;
        pmr->tx = (((float)(mty * myx) - (float)(mtx * myy))) / det;
        pmr->ty = (((float)(mtx * mxy) - (float)(mty * mxx))) / det;
    }
    return 0;
}
Exemple #11
0
/* Return gs_error_undefinedresult if the matrix is not invertible. */
int
gs_distance_transform_inverse(floatp dx, floatp dy,
                              const gs_matrix * pmat, gs_point * pdpt)
{
    if (is_xxyy(pmat)) {
        if (is_fzero(pmat->xx) || is_fzero(pmat->yy))
            return_error(gs_error_undefinedresult);
        pdpt->x = dx / pmat->xx;
        pdpt->y = dy / pmat->yy;
    } else if (is_xyyx(pmat)) {
        if (is_fzero(pmat->xy) || is_fzero(pmat->yx))
            return_error(gs_error_undefinedresult);
        pdpt->x = dy / pmat->xy;
        pdpt->y = dx / pmat->yx;
    } else {
        double det = pmat->xx * pmat->yy - pmat->xy * pmat->yx;

        if (det == 0)
            return_error(gs_error_undefinedresult);
        pdpt->x = (dx * pmat->yy - dy * pmat->yx) / det;
        pdpt->y = (dy * pmat->xx - dx * pmat->xy) / det;
    }
    return 0;
}
Exemple #12
0
int
gdev_vector_dopath(gx_device_vector *vdev, const gx_path * ppath,
                   gx_path_type_t type, const gs_matrix *pmat)
{
    bool do_close =
        (type & (gx_path_type_stroke | gx_path_type_always_close)) != 0;
    gs_fixed_rect rbox;
    gx_path_rectangular_type rtype = gx_path_is_rectangular(ppath, &rbox);
    gs_path_enum cenum;
    gdev_vector_dopath_state_t state;
    gs_fixed_point line_start, line_end;
    bool incomplete_line = false;
    bool need_moveto = false;
    int code;

    gdev_vector_dopath_init(&state, vdev, type, pmat);
    /*
     * if the path type is stroke, we only recognize closed
     * rectangles; otherwise, we recognize all rectangles.
     * Note that for stroking with a transformation, we can't use dorect,
     * which requires (untransformed) device coordinates.
     */
    if (rtype != prt_none &&
        (!(type & gx_path_type_stroke) || rtype == prt_closed) &&
        (pmat == 0 || is_xxyy(pmat) || is_xyyx(pmat)) &&
        (state.scale_mat.xx == 1.0 && state.scale_mat.yy == 1.0 &&
         is_xxyy(&state.scale_mat) &&
         is_fzero2(state.scale_mat.tx, state.scale_mat.ty))
        ) {
        gs_point p, q;

        gs_point_transform_inverse((double)rbox.p.x, (double)rbox.p.y,
                                   &state.scale_mat, &p);
        gs_point_transform_inverse((double)rbox.q.x, (double)rbox.q.y,
                                   &state.scale_mat, &q);
        code = vdev_proc(vdev, dorect)(vdev, (fixed)p.x, (fixed)p.y,
                                       (fixed)q.x, (fixed)q.y, type);
        if (code >= 0)
            return code;
        /* If the dorect proc failed, use a general path. */
    }
    code = vdev_proc(vdev, beginpath)(vdev, type);
    if (code < 0)
        return code;
    gx_path_enum_init(&cenum, ppath);
    for (;;) {
        gs_fixed_point vs[3];
        int pe_op = gx_path_enum_next(&cenum, vs);

    sw:
        if (type & gx_path_type_optimize) {
        opt:
            /* RJW: We fail to optimize gaptos */
            if (pe_op == gs_pe_lineto) {
                if (!incomplete_line) {
                    line_end = vs[0];
                    incomplete_line = true;
                    continue;
                }
                /*
                 * Merge collinear horizontal or vertical line segments
                 * going in the same direction.
                 */
                if (vs[0].x == line_end.x) {
                    if (vs[0].x == line_start.x &&
                        coord_between(line_start.y, line_end.y, vs[0].y)
                        ) {
                        line_end.y = vs[0].y;
                        continue;
                    }
                } else if (vs[0].y == line_end.y) {
                    if (vs[0].y == line_start.y &&
                        coord_between(line_start.x, line_end.x, vs[0].x)
                        ) {
                        line_end.x = vs[0].x;
                        continue;
                    }
                }
            }
            if (incomplete_line) {
                if (need_moveto) {	/* see gs_pe_moveto case */
                    code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
                                                      &line_start);
                    if (code < 0)
                        return code;
                    need_moveto = false;
                }
                code = gdev_vector_dopath_segment(&state, gs_pe_lineto,
                                                  &line_end);
                if (code < 0)
                    return code;
                line_start = line_end;
                incomplete_line = false;
                goto opt;
            }
        }
        switch (pe_op) {
        case 0:		/* done */
        done:
            code = vdev_proc(vdev, endpath)(vdev, type);
            return (code < 0 ? code : 0);
        case gs_pe_curveto:
            if (need_moveto) {	/* see gs_pe_moveto case */
                code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
                                                  &line_start);
                if (code < 0)
                    return code;
                need_moveto = false;
            }
            line_start = vs[2];
            goto draw;
        case gs_pe_moveto:
            /*
             * A bug in Acrobat Reader 4 causes it to draw a single pixel
             * for a fill with an isolated moveto.  If we're doing a fill
             * without a stroke, defer emitting a moveto until we know that
             * the subpath has more elements.
             */
            line_start = vs[0];
            if (!(type & gx_path_type_stroke) && (type & gx_path_type_fill)) {
                need_moveto = true;
                continue;
            }
            goto draw;
        case gs_pe_lineto:
        case gs_pe_gapto:
            if (need_moveto) {	/* see gs_pe_moveto case */
                code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
                                                  &line_start);
                if (code < 0)
                    return code;
                need_moveto = false;
            }
            line_start = vs[0];
            goto draw;
        case gs_pe_closepath:
            if (need_moveto) {	/* see gs_pe_moveto case */
                need_moveto = false;
                continue;
            }
            if (!do_close) {
                pe_op = gx_path_enum_next(&cenum, vs);
                if (pe_op == 0)
                    goto done;
                code = gdev_vector_dopath_segment(&state, gs_pe_closepath, vs);
                if (code < 0)
                    return code;
                goto sw;
            }
            /* falls through */
        draw:
            code = gdev_vector_dopath_segment(&state, pe_op, vs);
            if (code < 0)
                return code;
        }
        incomplete_line = false; /* only needed if optimizing */
    }
}
Exemple #13
0
static int
pdf_pattern(gx_device_pdf *pdev, const gx_drawing_color *pdc,
	    const gx_color_tile *p_tile, const gx_color_tile *m_tile,
	    cos_stream_t *pcs_image, pdf_resource_t **ppres)
{
    pdf_resource_t *pres;
    int code = pdf_alloc_resource(pdev, resourcePattern, pdc->mask.id, ppres,
				  0L);
    cos_stream_t *pcos;
    cos_dict_t *pcd;
    cos_dict_t *pcd_Resources = cos_dict_alloc(pdev, "pdf_pattern(Resources)");
    const gx_color_tile *tile = (p_tile ? p_tile : m_tile);
    const gx_strip_bitmap *btile = (p_tile ? &p_tile->tbits : &m_tile->tmask);
    bool mask = p_tile == 0;
    gs_point step;
    gs_matrix smat;

    if (code < 0)
	return code;
    if (!tile_size_ok(pdev, p_tile, m_tile))
	return_error(gs_error_limitcheck);
    /*
     * We currently can't handle Patterns whose X/Y step isn't parallel
     * to the coordinate axes.
     */
    if (is_xxyy(&tile->step_matrix))
	step.x = tile->step_matrix.xx, step.y = tile->step_matrix.yy;
    else if (is_xyyx(&tile->step_matrix))
	step.x = tile->step_matrix.yx, step.y = tile->step_matrix.xy;
    else
	return_error(gs_error_rangecheck);
    if (pcd_Resources == 0)
	return_error(gs_error_VMerror);
    gs_make_identity(&smat);
    smat.xx = btile->rep_width / (pdev->HWResolution[0] / 72.0);
    smat.yy = btile->rep_height / (pdev->HWResolution[1] / 72.0);
    smat.tx = tile->step_matrix.tx / (pdev->HWResolution[0] / 72.0);
    smat.ty = tile->step_matrix.ty / (pdev->HWResolution[1] / 72.0);
    pres = *ppres;
    {
	cos_dict_t *pcd_XObject = cos_dict_alloc(pdev, "pdf_pattern(XObject)");
	char key[MAX_REF_CHARS + 3];
	cos_value_t v;

	if (pcd_XObject == 0)
	    return_error(gs_error_VMerror);
	sprintf(key, "/R%ld", pcs_image->id);
	COS_OBJECT_VALUE(&v, pcs_image);
	if ((code = cos_dict_put(pcd_XObject, (byte *)key, strlen(key), &v)) < 0 ||
	    (code = cos_dict_put_c_key_object(pcd_Resources, "/XObject",
					      COS_OBJECT(pcd_XObject))) < 0
	    )
	    return code;
    }
    if ((code = cos_dict_put_c_strings(pcd_Resources, "/ProcSet",
				       (mask ? "[/PDF/ImageB]" :
					"[/PDF/ImageC]"))) < 0)
	return code;
    cos_become(pres->object, cos_type_stream);
    pcos = (cos_stream_t *)pres->object;
    pcd = cos_stream_dict(pcos);
    if ((code = cos_dict_put_c_key_int(pcd, "/PatternType", 1)) < 0 ||
	(code = cos_dict_put_c_key_int(pcd, "/PaintType",
				       (mask ? 2 : 1))) < 0 ||
	(code = cos_dict_put_c_key_int(pcd, "/TilingType",
				       tile->tiling_type)) < 0 ||
	(code = cos_dict_put_c_key_object(pcd, "/Resources",
					  COS_OBJECT(pcd_Resources))) < 0 ||
	(code = cos_dict_put_c_strings(pcd, "/BBox", "[0 0 1 1]")) < 0 ||
	(code = cos_dict_put_matrix(pcd, "/Matrix", &smat)) < 0 ||
	(code = cos_dict_put_c_key_real(pcd, "/XStep", step.x / btile->rep_width)) < 0 ||
	(code = cos_dict_put_c_key_real(pcd, "/YStep", step.y / btile->rep_height)) < 0
	) {
	return code;
    }

    {
	char buf[MAX_REF_CHARS + 6 + 1]; /* +6 for /R# Do\n */

	sprintf(buf, "/R%ld Do\n", pcs_image->id);
	cos_stream_add_bytes(pcos, (const byte *)buf, strlen(buf));
    }

    return 0;
}