[cairo-commit] 5 commits - src/cairo-output-stream.c src/cairo-output-stream-private.h src/cairo-pdf-operators.c

Adrian Johnson ajohnson at kemper.freedesktop.org
Thu Mar 20 01:24:11 PDT 2008


 src/cairo-output-stream-private.h |    3 -
 src/cairo-output-stream.c         |   89 ++++++++++++++++++++------------
 src/cairo-pdf-operators.c         |  104 ++++++++++++++++++++++++++++++++------
 3 files changed, 146 insertions(+), 50 deletions(-)

New commits:
commit 158b32b60bc7e0f6488383c1d4f83203ffe97c98
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Mar 18 22:29:59 2008 +1030

    PDF: Reduce excess decimals in text position offsets
    
    The numbers output in the TJ array for adjusting the horizontal
    position of the next glyph are in 1/1000 of the text space
    units. Rounding these numbers to an integer should still provide
    sufficient precision.
    
    We use the rounded numbers to update the text position in
    pdf-operators so subsequent numbers in the TJ array will compensate
    for the rounding error.

diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index 0c7ec58..9c14d61 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -887,10 +887,28 @@ _cairo_pdf_operators_show_glyphs (cairo_pdf_operators_t		*pdf_operators,
                 } else {
                     if (fabs((glyphs[i].x - Tm_x)/scaled_font->scale.xx) > GLYPH_POSITION_TOLERANCE) {
                         double delta = glyphs[i].x - Tm_x;
-
-                        _cairo_output_stream_printf (word_wrap_stream,
-                                                     "> %f <",
-                                                     -1000.0*delta/scaled_font->scale.xx);
+			int rounded_delta;
+
+			delta = -1000.0*delta/scaled_font->scale.xx;
+			/* As the delta is in 1/1000 of a unit of text
+			 * space, rounding to an integer should still
+			 * provide sufficient precision. We round the
+			 * delta before adding to Tm_x so that we keep
+			 * track of the accumulated rounding error in
+			 * the PDF interpreter and compensate for it
+			 * when calculating subsequent deltas.
+			 */
+			rounded_delta = _cairo_lround (delta);
+			if (rounded_delta != 0) {
+			    _cairo_output_stream_printf (word_wrap_stream,
+							 "> %d <",
+							 rounded_delta);
+			}
+
+			/* Convert the rounded delta back to cairo
+			 * space before adding to the current text
+			 * position. */
+			delta = rounded_delta*scaled_font->scale.xx/-1000.0;
                         Tm_x += delta;
                     }
                     _cairo_output_stream_printf (word_wrap_stream,
commit 222041530cd5d7f1ef6b41ea1738bf395ef1678a
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Mar 18 22:28:37 2008 +1030

    Use %g for printing path coordinates in pdf-operators
    
    to eliminate unnecessary decimal places in the output.

diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index cfd5d0a..0c7ec58 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -275,7 +275,7 @@ _cairo_pdf_path_move_to (void *closure, cairo_point_t *point)
     info->has_sub_path = FALSE;
     cairo_matrix_transform_point (info->path_transform, &x, &y);
     _cairo_output_stream_printf (info->output,
-				 "%f %f m ", x, y);
+				 "%g %g m ", x, y);
 
     return _cairo_output_stream_get_status (info->output);
 }
@@ -298,7 +298,7 @@ _cairo_pdf_path_line_to (void *closure, cairo_point_t *point)
     info->has_sub_path = TRUE;
     cairo_matrix_transform_point (info->path_transform, &x, &y);
     _cairo_output_stream_printf (info->output,
-				 "%f %f l ", x, y);
+				 "%g %g l ", x, y);
 
     return _cairo_output_stream_get_status (info->output);
 }
@@ -322,7 +322,7 @@ _cairo_pdf_path_curve_to (void          *closure,
     cairo_matrix_transform_point (info->path_transform, &cx, &cy);
     cairo_matrix_transform_point (info->path_transform, &dx, &dy);
     _cairo_output_stream_printf (info->output,
-				 "%f %f %f %f %f %f c ",
+				 "%g %g %g %g %g %g c ",
 				 bx, by, cx, cy, dx, dy);
     return _cairo_output_stream_get_status (info->output);
 }
@@ -355,7 +355,7 @@ _cairo_pdf_path_rectangle (pdf_path_info_t *info, cairo_box_t *box)
     cairo_matrix_transform_point (info->path_transform, &x1, &y1);
     cairo_matrix_transform_point (info->path_transform, &x2, &y2);
     _cairo_output_stream_printf (info->output,
-				 "%f %f %f %f re ",
+				 "%g %g %g %g re ",
 				 x1, y1, x2 - x1, y2 - y1);
 
     return _cairo_output_stream_get_status (info->output);
commit d78013470b11677df88f246d893c1f019f4ae228
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Mar 18 22:27:25 2008 +1030

    Add %g conversion specifer to output-stream for limited precision
    
    The %g conversion specifier is for printing numbers that were at some
    time stored in a cairo_fixed_t type and as a result have their
    precision limited by the size of CAIRO_FIXED_FRAC_BITS.
    
    Using %g will limit the number of digits after the decimal point to
    the minimum required to preserve the available precision.

diff --git a/src/cairo-output-stream.c b/src/cairo-output-stream.c
index 6c9401b..351585e 100644
--- a/src/cairo-output-stream.c
+++ b/src/cairo-output-stream.c
@@ -44,6 +44,26 @@
 #include <ctype.h>
 #include <errno.h>
 
+/* Numbers printed with %f are printed with this number of significant
+ * digits after the decimal.
+ */
+#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6
+
+/* Numbers printed with %g are assumed to only have CAIRO_FIXED_FRAC_BITS
+ * bits of precision available after the decimal point.
+ *
+ * FIXED_POINT_DECIMAL_DIGITS specifies the minimum number of decimal
+ * digits after the decimal point required to preserve the available
+ * precision.
+ *
+ * The conversion is:
+ *
+ * FIXED_POINT_DECIMAL_DIGITS = ceil( CAIRO_FIXED_FRAC_BITS * ln(2)/ln(10) )
+ *
+ * We can replace ceil(x) with (int)(x+1) since x will never be an
+ * integer for any likely value of CAIRO_FIXED_FRAC_BITS.
+ */
+#define FIXED_POINT_DECIMAL_DIGITS ((int)(CAIRO_FIXED_FRAC_BITS*0.301029996 + 1))
 
 void
 _cairo_output_stream_init (cairo_output_stream_t            *stream,
@@ -236,8 +256,6 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
     }
 }
 
-#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6
-
 /* Format a double in a locale independent way and trim trailing
  * zeros.  Based on code from Alex Larson <alexl at redhat.com>.
  * http://mail.gnome.org/archives/gtk-devel-list/2001-October/msg00087.html
@@ -247,7 +265,7 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
  * into cairo (see COPYING). -- Kristian Høgsberg <krh at redhat.com>
  */
 static void
-_cairo_dtostr (char *buffer, size_t size, double d)
+_cairo_dtostr (char *buffer, size_t size, double d, cairo_bool_t limited_precision)
 {
     struct lconv *locale_data;
     const char *decimal_point;
@@ -266,40 +284,44 @@ _cairo_dtostr (char *buffer, size_t size, double d)
 
     assert (decimal_point_len != 0);
 
-    /* Using "%f" to print numbers less than 0.1 will result in
-     * reduced precision due to the default 6 digits after the
-     * decimal point.
-     *
-     * For numbers is < 0.1, we print with maximum precision and count
-     * the number of zeros between the decimal point and the first
-     * significant digit. We then print the number again with the
-     * number of decimal places that gives us the required number of
-     * significant digits. This ensures the number is correctly
-     * rounded.
-     */
-    if (fabs (d) >= 0.1) {
-	snprintf (buffer, size, "%f", d);
+    if (limited_precision) {
+	snprintf (buffer, size, "%.*f", FIXED_POINT_DECIMAL_DIGITS, d);
     } else {
-	snprintf (buffer, size, "%.18f", d);
-	p = buffer;
+	/* Using "%f" to print numbers less than 0.1 will result in
+	 * reduced precision due to the default 6 digits after the
+	 * decimal point.
+	 *
+	 * For numbers is < 0.1, we print with maximum precision and count
+	 * the number of zeros between the decimal point and the first
+	 * significant digit. We then print the number again with the
+	 * number of decimal places that gives us the required number of
+	 * significant digits. This ensures the number is correctly
+	 * rounded.
+	 */
+	if (fabs (d) >= 0.1) {
+	    snprintf (buffer, size, "%f", d);
+	} else {
+	    snprintf (buffer, size, "%.18f", d);
+	    p = buffer;
 
-	if (*p == '+' || *p == '-')
-	    p++;
+	    if (*p == '+' || *p == '-')
+		p++;
 
-	while (isdigit (*p))
-	    p++;
+	    while (isdigit (*p))
+		p++;
 
-	if (strncmp (p, decimal_point, decimal_point_len) == 0)
-	    p += decimal_point_len;
+	    if (strncmp (p, decimal_point, decimal_point_len) == 0)
+		p += decimal_point_len;
 
-	num_zeros = 0;
-	while (*p++ == '0')
-	    num_zeros++;
+	    num_zeros = 0;
+	    while (*p++ == '0')
+		num_zeros++;
 
-	decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL;
+	    decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL;
 
-	if (decimal_digits < 18)
-	    snprintf (buffer, size, "%.*f", decimal_digits, d);
+	    if (decimal_digits < 18)
+		snprintf (buffer, size, "%.*f", decimal_digits, d);
+	}
     }
     p = buffer;
 
@@ -441,7 +463,10 @@ _cairo_output_stream_vprintf (cairo_output_stream_t *stream,
 		      single_fmt, va_arg (ap, const char *));
 	    break;
 	case 'f':
-	    _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double));
+	    _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), FALSE);
+	    break;
+	case 'g':
+	    _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), TRUE);
 	    break;
 	case 'c':
 	    buffer[0] = va_arg (ap, int);
commit f3734085a1d1d9b08004a243e28a0233f621847c
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Mar 18 22:25:46 2008 +1030

    Make _cairo_dtostr() static
    
    It is only used in cairo-output-stream.c

diff --git a/src/cairo-output-stream-private.h b/src/cairo-output-stream-private.h
index 9e9e3ad..1a25788 100644
--- a/src/cairo-output-stream-private.h
+++ b/src/cairo-output-stream-private.h
@@ -111,9 +111,6 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
 				       size_t length);
 
 cairo_private void
-_cairo_dtostr (char *buffer, size_t size, double d);
-
-cairo_private void
 _cairo_output_stream_vprintf (cairo_output_stream_t *stream,
 			      const char *fmt,
 			      va_list ap) CAIRO_PRINTF_FORMAT ( 2, 0);
diff --git a/src/cairo-output-stream.c b/src/cairo-output-stream.c
index b337994..6c9401b 100644
--- a/src/cairo-output-stream.c
+++ b/src/cairo-output-stream.c
@@ -246,7 +246,7 @@ _cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
  * has been relicensed under the LGPL/MPL dual license for inclusion
  * into cairo (see COPYING). -- Kristian Høgsberg <krh at redhat.com>
  */
-void
+static void
 _cairo_dtostr (char *buffer, size_t size, double d)
 {
     struct lconv *locale_data;
commit 4d9e5b51aa4164cf758c6a1f84c7f831becc098b
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Mar 18 22:25:18 2008 +1030

    Rescale CTM used for PS/PDF stroking to reduce rounding error
    
    The PS/PDF CTM is transformed to the user space CTM when emitting
    paths to be stroked to ensure the correct pen shape used. However this
    can result in rounding errors.
    
    For example the device space point (1.234, 3.142) when transformed to
    a user space CTM of [100 0 0 100 0 0] will be emitted as
    (0.012, 0.031).
    
    Fix this by rescaling the user space CTM so that the path coordinates
    emitted to the PDF file stay within the range the maximizes the
    precision. The line width and dashing is also rescaled to be the same
    size in the rescaled CTM.

diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index c0a8a9c..cfd5d0a 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -480,7 +480,8 @@ _cairo_pdf_line_join (cairo_line_join_t join)
 
 static cairo_int_status_t
 _cairo_pdf_operators_emit_stroke_style (cairo_pdf_operators_t	*pdf_operators,
-					cairo_stroke_style_t	*style)
+					cairo_stroke_style_t	*style,
+					double			 scale)
 {
     double *dash = style->dash;
     int num_dashes = style->num_dashes;
@@ -551,7 +552,7 @@ _cairo_pdf_operators_emit_stroke_style (cairo_pdf_operators_t	*pdf_operators,
 
     _cairo_output_stream_printf (pdf_operators->stream,
 				 "%f w\n",
-				 style->line_width);
+				 style->line_width * scale);
 
     _cairo_output_stream_printf (pdf_operators->stream,
 				 "%d J\n",
@@ -566,9 +567,9 @@ _cairo_pdf_operators_emit_stroke_style (cairo_pdf_operators_t	*pdf_operators,
 
 	_cairo_output_stream_printf (pdf_operators->stream, "[");
 	for (d = 0; d < num_dashes; d++)
-	    _cairo_output_stream_printf (pdf_operators->stream, " %f", dash[d]);
+	    _cairo_output_stream_printf (pdf_operators->stream, " %f", dash[d] * scale);
 	_cairo_output_stream_printf (pdf_operators->stream, "] %f d\n",
-				     dash_offset);
+				     dash_offset * scale);
     } else {
 	_cairo_output_stream_printf (pdf_operators->stream, "[] 0.0 d\n");
     }
@@ -582,6 +583,31 @@ _cairo_pdf_operators_emit_stroke_style (cairo_pdf_operators_t	*pdf_operators,
     return _cairo_output_stream_get_status (pdf_operators->stream);
 }
 
+/* Scale the matrix so the largest absolute value of the non
+ * translation components is 1.0. Return the scale required to restore
+ * the matrix to the original values.
+ *
+ * eg the matrix  [ 100  0  0  50   20   10  ]
+ *
+ * is rescaled to [  1   0  0  0.5  0.2  0.1 ]
+ * and the scale returned is 100
+ */
+static void
+_cairo_matrix_factor_out_scale (cairo_matrix_t *m, double *scale)
+{
+    double s;
+
+    s = fabs (m->xx);
+    if (fabs (m->xy) > s)
+	s = fabs (m->xy);
+    if (fabs (m->yx) > s)
+	s = fabs (m->yx);
+    if (fabs (m->yy) > s)
+	s = fabs (m->yy);
+    *scale = s;
+    s = 1.0/s;
+    cairo_matrix_scale (m, s, s);
+}
 
 static cairo_int_status_t
 _cairo_pdf_operators_emit_stroke (cairo_pdf_operators_t	*pdf_operators,
@@ -594,6 +620,7 @@ _cairo_pdf_operators_emit_stroke (cairo_pdf_operators_t	*pdf_operators,
     cairo_status_t status;
     cairo_matrix_t m, path_transform;
     cairo_bool_t has_ctm = TRUE;
+    double scale = 1.0;
 
     /* Optimize away the stroke ctm when it does not affect the
      * stroke. There are other ctm cases that could be optimized
@@ -605,15 +632,44 @@ _cairo_pdf_operators_emit_stroke (cairo_pdf_operators_t	*pdf_operators,
 	has_ctm = FALSE;
     }
 
-    status = _cairo_pdf_operators_emit_stroke_style (pdf_operators, style);
+    /* The PDF CTM is transformed to the user space CTM when stroking
+     * so the corect pen shape will be used. This also requires that
+     * the path be transformed to user space when emitted. The
+     * conversion of path coordinates to user space may cause rounding
+     * errors. For example the device space point (1.234, 3.142) when
+     * transformed to a user space CTM of [100 0 0 100 0 0] will be
+     * emitted as (0.012, 0.031).
+     *
+     * To avoid the rounding problem we scale the user space CTM
+     * matrix so that all the non translation components of the matrix
+     * are <= 1. The line width and and dashes are scaled by the
+     * inverse of the scale applied to the CTM. This maintains the
+     * shape of the stroke pen while keeping the user space CTM within
+     * the range that maximizes the precision of the emitted path.
+     */
+    if (has_ctm) {
+	m = *ctm;
+	/* Zero out the translation since it does not affect the pen
+	 * shape however it may cause unnecessary digits to be emitted.
+	 */
+	m.x0 = 0.0;
+	m.y0 = 0.0;
+	_cairo_matrix_factor_out_scale (&m, &scale);
+	path_transform = m;
+	status = cairo_matrix_invert (&path_transform);
+	if (status)
+	    return status;
+
+	cairo_matrix_multiply (&m, &m, &pdf_operators->cairo_to_pdf);
+    }
+
+    status = _cairo_pdf_operators_emit_stroke_style (pdf_operators, style, scale);
     if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
 	return CAIRO_STATUS_SUCCESS;
     if (status)
 	return status;
 
     if (has_ctm) {
-	cairo_matrix_multiply (&m, ctm, &pdf_operators->cairo_to_pdf);
-	path_transform = *ctm_inverse;
 	_cairo_output_stream_printf (pdf_operators->stream,
 				     "q %f %f %f %f %f %f cm\n",
 				     m.xx, m.yx, m.xy, m.yy,


More information about the cairo-commit mailing list