[cairo-commit] 2 commits - src/cairo-backend-private.h src/cairo.c src/cairo-compositor.c src/cairo-default-context.c src/cairo-gstate.c src/cairo-gstate-private.h src/cairo.h src/cairoint.h src/cairo-script-surface.c src/cairo-stroke-style.c src/cairo-svg-surface.c src/cairo-types-private.h src/win32 test/hairline.c test/Makefile.sources test/meson.build test/reference util/cairo-script

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Sun Aug 15 06:58:58 UTC 2021


 src/cairo-backend-private.h                                        |    2 
 src/cairo-compositor.c                                             |   66 +++
 src/cairo-default-context.c                                        |   18 +
 src/cairo-gstate-private.h                                         |    6 
 src/cairo-gstate.c                                                 |   30 +
 src/cairo-script-surface.c                                         |   22 +
 src/cairo-stroke-style.c                                           |    4 
 src/cairo-svg-surface.c                                            |   24 -
 src/cairo-types-private.h                                          |    2 
 src/cairo.c                                                        |   61 +++
 src/cairo.h                                                        |    6 
 src/cairoint.h                                                     |    2 
 src/win32/cairo-win32-printing-surface.c                           |   10 
 test/Makefile.sources                                              |    1 
 test/hairline.c                                                    |  175 ++++++++++
 test/meson.build                                                   |    1 
 test/reference/hairline-anisotropic-incorrect.image16.ref.png      |binary
 test/reference/hairline-anisotropic-incorrect.pdf.ref.png          |binary
 test/reference/hairline-anisotropic-incorrect.quartz.ref.png       |binary
 test/reference/hairline-anisotropic-incorrect.ref.png              |binary
 test/reference/hairline-anisotropic-incorrect.svg11.argb32.ref.png |binary
 test/reference/hairline-anisotropic-incorrect.svg11.rgb24.ref.png  |binary
 test/reference/hairline-anisotropic-incorrect.xcb-window&.ref.png  |binary
 test/reference/hairline-anisotropic-incorrect.xcb-window.ref.png   |binary
 test/reference/hairline-anisotropic-incorrect.xcb.ref.png          |binary
 test/reference/hairline-anisotropic-incorrect.xlib-window.ref.png  |binary
 test/reference/hairline-anisotropic-incorrect.xlib.ref.png         |binary
 test/reference/hairline-anisotropic.image16.ref.png                |binary
 test/reference/hairline-anisotropic.pdf.ref.png                    |binary
 test/reference/hairline-anisotropic.quartz.ref.png                 |binary
 test/reference/hairline-anisotropic.ref.png                        |binary
 test/reference/hairline-anisotropic.xcb-window&.ref.png            |binary
 test/reference/hairline-anisotropic.xcb-window.ref.png             |binary
 test/reference/hairline-anisotropic.xcb.ref.png                    |binary
 test/reference/hairline-anisotropic.xlib-window.ref.png            |binary
 test/reference/hairline-anisotropic.xlib.ref.png                   |binary
 test/reference/hairline-big.image16.ref.png                        |binary
 test/reference/hairline-big.pdf.ref.png                            |binary
 test/reference/hairline-big.quartz.ref.png                         |binary
 test/reference/hairline-big.ref.png                                |binary
 test/reference/hairline-big.xcb-window&.ref.png                    |binary
 test/reference/hairline-big.xcb-window.ref.png                     |binary
 test/reference/hairline-big.xcb.ref.png                            |binary
 test/reference/hairline-big.xlib-window.ref.png                    |binary
 test/reference/hairline-big.xlib.ref.png                           |binary
 test/reference/hairline-scaled.image16.ref.png                     |binary
 test/reference/hairline-scaled.pdf.ref.png                         |binary
 test/reference/hairline-scaled.quartz.ref.png                      |binary
 test/reference/hairline-scaled.ref.png                             |binary
 test/reference/hairline-scaled.svg11.ref.png                       |binary
 test/reference/hairline-scaled.xcb-window&.ref.png                 |binary
 test/reference/hairline-scaled.xcb-window.ref.png                  |binary
 test/reference/hairline-scaled.xcb.ref.png                         |binary
 test/reference/hairline-scaled.xlib-window.ref.png                 |binary
 test/reference/hairline-scaled.xlib.ref.png                        |binary
 test/reference/hairline.image16.ref.png                            |binary
 test/reference/hairline.pdf.ref.png                                |binary
 test/reference/hairline.quartz.ref.png                             |binary
 test/reference/hairline.ref.png                                    |binary
 test/reference/hairline.svg11.ref.png                              |binary
 test/reference/hairline.xcb-window&.ref.png                        |binary
 test/reference/hairline.xcb-window.ref.png                         |binary
 test/reference/hairline.xcb.ref.png                                |binary
 test/reference/hairline.xlib-window.ref.png                        |binary
 test/reference/hairline.xlib.ref.png                               |binary
 util/cairo-script/cairo-script-operators.c                         |   22 +
 66 files changed, 427 insertions(+), 25 deletions(-)

New commits:
commit c773060195c5aee7d84208188bed5740a28747a8
Merge: 9f973bc43 ecec0419f
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Aug 15 06:58:54 2021 +0000

    Merge branch 'HairlineStroke' into 'master'
    
    Added hairline support to cairo
    
    See merge request cairo/cairo!21

commit ecec0419f8e178d71e449b52acfdfe9ac03aed37
Author: Rick Yorgason <rick at firefang.com>
Date:   Sun Aug 15 06:58:54 2021 +0000

    Added hairline support to cairo

diff --git a/src/cairo-backend-private.h b/src/cairo-backend-private.h
index 67607c12f..86689c795 100644
--- a/src/cairo-backend-private.h
+++ b/src/cairo-backend-private.h
@@ -68,6 +68,7 @@ struct _cairo_backend {
     cairo_status_t (*set_line_cap) (void *cr, cairo_line_cap_t line_cap);
     cairo_status_t (*set_line_join) (void *cr, cairo_line_join_t line_join);
     cairo_status_t (*set_line_width) (void *cr, double line_width);
+    cairo_status_t (*set_hairline) (void *cr, cairo_bool_t set_hairline);
     cairo_status_t (*set_miter_limit) (void *cr, double limit);
     cairo_status_t (*set_opacity) (void *cr, double opacity);
     cairo_status_t (*set_operator) (void *cr, cairo_operator_t op);
@@ -79,6 +80,7 @@ struct _cairo_backend {
     cairo_line_cap_t (*get_line_cap) (void *cr);
     cairo_line_join_t (*get_line_join) (void *cr);
     double (*get_line_width) (void *cr);
+    cairo_bool_t (*get_hairline) (void *cr);
     double (*get_miter_limit) (void *cr);
     double (*get_opacity) (void *cr);
     cairo_operator_t (*get_operator) (void *cr);
diff --git a/src/cairo-compositor.c b/src/cairo-compositor.c
index b31413b99..2f49f7f20 100644
--- a/src/cairo-compositor.c
+++ b/src/cairo-compositor.c
@@ -122,18 +122,18 @@ _cairo_compositor_mask (const cairo_compositor_t	*compositor,
     return status;
 }
 
-cairo_int_status_t
-_cairo_compositor_stroke (const cairo_compositor_t	*compositor,
-			  cairo_surface_t		*surface,
-			  cairo_operator_t		 op,
-			  const cairo_pattern_t		*source,
-			  const cairo_path_fixed_t	*path,
-			  const cairo_stroke_style_t	*style,
-			  const cairo_matrix_t		*ctm,
-			  const cairo_matrix_t		*ctm_inverse,
-			  double			 tolerance,
-			  cairo_antialias_t		 antialias,
-			  const cairo_clip_t		*clip)
+static cairo_int_status_t
+_cairo_compositor_stroke_impl (const cairo_compositor_t	*compositor,
+			       cairo_surface_t		*surface,
+			       cairo_operator_t		 op,
+			       const cairo_pattern_t		*source,
+			       const cairo_path_fixed_t	*path,
+			       const cairo_stroke_style_t	*style,
+			       const cairo_matrix_t		*ctm,
+			       const cairo_matrix_t		*ctm_inverse,
+			       double			 tolerance,
+			       cairo_antialias_t		 antialias,
+			       const cairo_clip_t		*clip)
 {
     cairo_composite_rectangles_t extents;
     cairo_int_status_t status;
@@ -175,6 +175,48 @@ _cairo_compositor_stroke (const cairo_compositor_t	*compositor,
     return status;
 }
 
+cairo_int_status_t
+_cairo_compositor_stroke (const cairo_compositor_t	*compositor,
+			  cairo_surface_t		*surface,
+			  cairo_operator_t		 op,
+			  const cairo_pattern_t	*source,
+			  const cairo_path_fixed_t	*path,
+			  const cairo_stroke_style_t	*style,
+			  const cairo_matrix_t		*ctm,
+			  const cairo_matrix_t		*ctm_inverse,
+			  double			 tolerance,
+			  cairo_antialias_t		 antialias,
+			  const cairo_clip_t		*clip)
+{
+    if (!style->is_hairline)
+	return _cairo_compositor_stroke_impl (compositor, surface,
+				              op, source, path,
+					      style, ctm, ctm_inverse,
+					      tolerance, antialias, clip);
+    else {
+	cairo_stroke_style_t hairline_style;
+	cairo_status_t status;
+	cairo_matrix_t identity;
+
+	status = _cairo_stroke_style_init_copy (&hairline_style, style);
+	if (unlikely (status))
+	    return status;
+	
+	hairline_style.line_width = 1.0;
+
+	cairo_matrix_init_identity (&identity);
+
+	status = _cairo_compositor_stroke_impl (compositor, surface,
+					        op, source, path,
+					        &hairline_style, &identity, &identity,
+					        tolerance, antialias, clip);
+
+	_cairo_stroke_style_fini (&hairline_style);
+
+	return status;
+    }
+}
+
 cairo_int_status_t
 _cairo_compositor_fill (const cairo_compositor_t	*compositor,
 			cairo_surface_t			*surface,
diff --git a/src/cairo-default-context.c b/src/cairo-default-context.c
index d2c9cae10..567c5d4d5 100644
--- a/src/cairo-default-context.c
+++ b/src/cairo-default-context.c
@@ -403,6 +403,14 @@ _cairo_default_context_set_line_width (void *abstract_cr,
     return _cairo_gstate_set_line_width (cr->gstate, line_width);
 }
 
+static cairo_status_t
+_cairo_default_context_set_hairline (void *abstract_cr, cairo_bool_t set_hairline)
+{
+    cairo_default_context_t *cr = abstract_cr;
+
+    return _cairo_gstate_set_hairline (cr->gstate, set_hairline);
+}
+
 static cairo_status_t
 _cairo_default_context_set_line_cap (void *abstract_cr,
 				     cairo_line_cap_t line_cap)
@@ -477,6 +485,14 @@ _cairo_default_context_get_line_width (void *abstract_cr)
     return _cairo_gstate_get_line_width (cr->gstate);
 }
 
+static cairo_bool_t
+_cairo_default_context_get_hairline (void *abstract_cr)
+{
+    cairo_default_context_t *cr = abstract_cr;
+
+    return _cairo_gstate_get_hairline (cr->gstate);
+}
+
 static cairo_line_cap_t
 _cairo_default_context_get_line_cap (void *abstract_cr)
 {
@@ -1365,6 +1381,7 @@ static const cairo_backend_t _cairo_default_context_backend = {
     _cairo_default_context_set_line_cap,
     _cairo_default_context_set_line_join,
     _cairo_default_context_set_line_width,
+    _cairo_default_context_set_hairline,
     _cairo_default_context_set_miter_limit,
     _cairo_default_context_set_opacity,
     _cairo_default_context_set_operator,
@@ -1375,6 +1392,7 @@ static const cairo_backend_t _cairo_default_context_backend = {
     _cairo_default_context_get_line_cap,
     _cairo_default_context_get_line_join,
     _cairo_default_context_get_line_width,
+    _cairo_default_context_get_hairline,
     _cairo_default_context_get_miter_limit,
     _cairo_default_context_get_opacity,
     _cairo_default_context_get_operator,
diff --git a/src/cairo-gstate-private.h b/src/cairo-gstate-private.h
index 198c66998..fe1556b03 100644
--- a/src/cairo-gstate-private.h
+++ b/src/cairo-gstate-private.h
@@ -139,6 +139,12 @@ _cairo_gstate_set_line_width (cairo_gstate_t *gstate, double width);
 cairo_private double
 _cairo_gstate_get_line_width (cairo_gstate_t *gstate);
 
+cairo_private cairo_status_t
+_cairo_gstate_set_hairline (cairo_gstate_t *gstate, cairo_bool_t set_hairline);
+
+cairo_private cairo_bool_t
+_cairo_gstate_get_hairline (cairo_gstate_t *gstate);
+
 cairo_private cairo_status_t
 _cairo_gstate_set_line_cap (cairo_gstate_t *gstate, cairo_line_cap_t line_cap);
 
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 64060c4fc..a8c67e718 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -470,7 +470,10 @@ _cairo_gstate_get_fill_rule (cairo_gstate_t *gstate)
 cairo_status_t
 _cairo_gstate_set_line_width (cairo_gstate_t *gstate, double width)
 {
-    gstate->stroke_style.line_width = width;
+    if (gstate->stroke_style.is_hairline)
+	gstate->stroke_style.pre_hairline_line_width = width;
+	else
+	gstate->stroke_style.line_width = width;
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -481,6 +484,29 @@ _cairo_gstate_get_line_width (cairo_gstate_t *gstate)
     return gstate->stroke_style.line_width;
 }
 
+cairo_status_t
+_cairo_gstate_set_hairline (cairo_gstate_t *gstate, cairo_bool_t set_hairline)
+{
+    if (gstate->stroke_style.is_hairline != set_hairline) {
+        gstate->stroke_style.is_hairline = set_hairline;
+
+        if (set_hairline) {
+            gstate->stroke_style.pre_hairline_line_width = gstate->stroke_style.line_width;
+            gstate->stroke_style.line_width = 0.0;
+        } else {
+            gstate->stroke_style.line_width = gstate->stroke_style.pre_hairline_line_width;
+        }
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_bool_t
+_cairo_gstate_get_hairline (cairo_gstate_t *gstate)
+{
+    return gstate->stroke_style.is_hairline;
+}
+
 cairo_status_t
 _cairo_gstate_set_line_cap (cairo_gstate_t *gstate, cairo_line_cap_t line_cap)
 {
@@ -1172,7 +1198,7 @@ _cairo_gstate_stroke (cairo_gstate_t *gstate, cairo_path_fixed_t *path)
     if (gstate->op == CAIRO_OPERATOR_DEST)
 	return CAIRO_STATUS_SUCCESS;
 
-    if (gstate->stroke_style.line_width <= 0.0)
+    if (gstate->stroke_style.line_width <= 0.0 && !gstate->stroke_style.is_hairline)
 	return CAIRO_STATUS_SUCCESS;
 
     if (_cairo_clip_is_all_clipped (gstate->clip))
diff --git a/src/cairo-script-surface.c b/src/cairo-script-surface.c
index 800db0780..9d6b954c1 100644
--- a/src/cairo-script-surface.c
+++ b/src/cairo-script-surface.c
@@ -708,6 +708,24 @@ _emit_line_width (cairo_script_surface_t *surface,
     return CAIRO_STATUS_SUCCESS;
 }
 
+static cairo_status_t
+_emit_hairline (cairo_script_surface_t *surface, cairo_bool_t set_hairline)
+{
+    assert (target_is_active (surface));
+
+    if (surface->cr.current_style.is_hairline == set_hairline)
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    surface->cr.current_style.is_hairline = set_hairline;
+
+    _cairo_output_stream_printf (to_context (surface)->stream, 
+					"%d set-hairline\n",
+					set_hairline);
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static cairo_status_t
 _emit_line_cap (cairo_script_surface_t *surface,
 		cairo_line_cap_t line_cap)
@@ -858,6 +876,10 @@ _emit_stroke_style (cairo_script_surface_t *surface,
     if (unlikely (status))
 	return status;
 
+    status = _emit_hairline (surface, style->is_hairline);
+    if (unlikely (status))
+	return status;
+
     status = _emit_dash (surface,
 			 style->dash, style->num_dashes, style->dash_offset,
 			 force);
diff --git a/src/cairo-stroke-style.c b/src/cairo-stroke-style.c
index 9c373c333..856cb341b 100644
--- a/src/cairo-stroke-style.c
+++ b/src/cairo-stroke-style.c
@@ -49,6 +49,8 @@ _cairo_stroke_style_init (cairo_stroke_style_t *style)
     style->dash = NULL;
     style->num_dashes = 0;
     style->dash_offset = 0.0;
+
+    style->is_hairline = FALSE;
 }
 
 cairo_status_t
@@ -80,6 +82,8 @@ _cairo_stroke_style_init_copy (cairo_stroke_style_t *style,
 
     style->dash_offset = other->dash_offset;
 
+    style->is_hairline = other->is_hairline;
+
     return CAIRO_STATUS_SUCCESS;
 }
 
diff --git a/src/cairo-svg-surface.c b/src/cairo-svg-surface.c
index 412b32aff..fb6081c48 100644
--- a/src/cairo-svg-surface.c
+++ b/src/cairo-svg-surface.c
@@ -2992,13 +2992,23 @@ _cairo_svg_surface_emit_stroke_style (cairo_svg_stream_t *output,
 	ASSERT_NOT_REACHED;
     }
 
-    _cairo_svg_stream_printf (output,
-			      " stroke-width=\"%f\""
-			      " stroke-linecap=\"%s\""
-			      " stroke-linejoin=\"%s\"",
-			      stroke_style->line_width,
-			      line_cap,
-			      line_join);
+    if (stroke_style->is_hairline) {
+		_cairo_svg_stream_printf (output,
+					" stroke-width=\"1px\""
+					" stroke-linecap=\"%s\""
+					" stroke-linejoin=\"%s\""
+					" style=\"vector-effect: non-scaling-stroke\"",
+					line_cap,
+					line_join);
+	} else {
+		_cairo_svg_stream_printf (output,
+					" stroke-width=\"%f\""
+					" stroke-linecap=\"%s\""
+					" stroke-linejoin=\"%s\"",
+					stroke_style->line_width,
+					line_cap,
+					line_join);
+	}
 
     status = _cairo_svg_surface_emit_pattern (surface, source, output, TRUE, parent_matrix);
     if (unlikely (status)) {
diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h
index 0f46214ed..2ec2ce67b 100644
--- a/src/cairo-types-private.h
+++ b/src/cairo-types-private.h
@@ -376,6 +376,8 @@ typedef struct _cairo_stroke_style {
     double		*dash;
     unsigned int	 num_dashes;
     double		 dash_offset;
+    cairo_bool_t	 is_hairline;
+    double      pre_hairline_line_width;
 } cairo_stroke_style_t;
 
 typedef struct _cairo_format_masks {
diff --git a/src/cairo.c b/src/cairo.c
index b2bda657d..d141b56d2 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -1184,6 +1184,47 @@ cairo_set_line_width (cairo_t *cr, double width)
 }
 slim_hidden_def (cairo_set_line_width);
 
+/**
+ * cairo_set_hairline:
+ * @cr: a #cairo_t
+ * @set_hairline: whether or not to set hairline mode
+ *
+ * Sets lines within the cairo context to be hairlines.
+ * Hairlines are logically zero-width lines that are drawn at the
+ * thinnest renderable width possible in the current context.
+ *
+ * On surfaces with native hairline support, the native hairline
+ * functionality will be used. Surfaces that support hairlines include:
+ * - pdf/ps: Encoded as 0-width line.
+ * - win32_printing: Rendered with PS_COSMETIC pen.
+ * - svg: Encoded as 1px non-scaling-stroke.
+ * - script: Encoded with set-hairline function.
+ *
+ * Cairo will always render hairlines at 1 device unit wide, even if
+ * an anisotropic scaling was applied to the stroke width. In the wild,
+ * handling of this situation is not well-defined. Some PDF, PS, and SVG
+ * renderers match Cairo's output, but some very popular implementations
+ * (Acrobat, Chrome, rsvg) will scale the hairline unevenly.
+ * As such, best practice is to reset any anisotropic scaling before calling
+ * cairo_stroke(). See https://cairographics.org/cookbook/ellipses/
+ * for an example.
+ *
+ * Since: 1.18
+ **/
+void
+cairo_set_hairline (cairo_t *cr, cairo_bool_t set_hairline)
+{
+    cairo_status_t status;
+
+    if (unlikely (cr->status))
+	return;
+
+    status = cr->backend->set_hairline (cr, set_hairline);
+    if (unlikely (status))
+	_cairo_set_error (cr, status);
+}
+slim_hidden_def (cairo_set_hairline);
+
 /**
  * cairo_set_line_cap:
  * @cr: a cairo context
@@ -4057,6 +4098,26 @@ cairo_get_line_width (cairo_t *cr)
 }
 slim_hidden_def (cairo_get_line_width);
 
+/**
+ * cairo_get_hairline:
+ * @cr: a cairo context
+ *
+ * Returns whether or not hairline mode is set, as set by cairo_set_hairline().
+ *
+ * Return value: whether hairline mode is set.
+ *
+ * Since: 1.18
+ **/
+cairo_bool_t
+cairo_get_hairline (cairo_t *cr)
+{
+    if (unlikely (cr->status))
+        return FALSE;
+
+    return cr->backend->get_hairline (cr);
+}
+slim_hidden_def (cairo_get_hairline);
+
 /**
  * cairo_get_line_cap:
  * @cr: a cairo context
diff --git a/src/cairo.h b/src/cairo.h
index 96427b425..5ab47c112 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -765,6 +765,9 @@ cairo_set_fill_rule (cairo_t *cr, cairo_fill_rule_t fill_rule);
 cairo_public void
 cairo_set_line_width (cairo_t *cr, double width);
 
+cairo_public void
+cairo_set_hairline (cairo_t *cr, cairo_bool_t set_hairline);
+
 /**
  * cairo_line_cap_t:
  * @CAIRO_LINE_CAP_BUTT: start(stop) the line exactly at the start(end) point (Since 1.0)
@@ -1957,6 +1960,9 @@ cairo_get_fill_rule (cairo_t *cr);
 cairo_public double
 cairo_get_line_width (cairo_t *cr);
 
+cairo_public cairo_bool_t
+cairo_get_hairline (cairo_t *cr);
+
 cairo_public cairo_line_cap_t
 cairo_get_line_cap (cairo_t *cr);
 
diff --git a/src/cairoint.h b/src/cairoint.h
index 64ed2aa4d..6affe5601 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1959,6 +1959,7 @@ slim_hidden_proto (cairo_font_options_status);
 slim_hidden_proto (cairo_format_stride_for_width);
 slim_hidden_proto (cairo_get_current_point);
 slim_hidden_proto (cairo_get_line_width);
+slim_hidden_proto (cairo_get_hairline);
 slim_hidden_proto (cairo_get_matrix);
 slim_hidden_proto (cairo_get_scaled_font);
 slim_hidden_proto (cairo_get_target);
@@ -2031,6 +2032,7 @@ slim_hidden_proto (cairo_set_font_size);
 slim_hidden_proto (cairo_set_line_cap);
 slim_hidden_proto (cairo_set_line_join);
 slim_hidden_proto (cairo_set_line_width);
+slim_hidden_proto (cairo_set_hairline);
 slim_hidden_proto (cairo_set_matrix);
 slim_hidden_proto (cairo_set_operator);
 slim_hidden_proto (cairo_set_source);
diff --git a/src/win32/cairo-win32-printing-surface.c b/src/win32/cairo-win32-printing-surface.c
index 094068c15..6e3636104 100644
--- a/src/win32/cairo-win32-printing-surface.c
+++ b/src/win32/cairo-win32-printing-surface.c
@@ -1520,7 +1520,7 @@ _cairo_win32_printing_surface_stroke (void			*abstract_surface,
     cairo_matrix_multiply (&mat, stroke_ctm, &surface->ctm);
     _cairo_matrix_factor_out_scale (&mat, &scale);
 
-    pen_style = PS_GEOMETRIC;
+    pen_style = style->is_hairline ? PS_COSMETIC : PS_GEOMETRIC;
     dash_array = NULL;
     if (style->num_dashes) {
 	pen_style |= PS_USERSTYLE;
@@ -1546,10 +1546,12 @@ _cairo_win32_printing_surface_stroke (void			*abstract_surface,
     brush.lbStyle = BS_SOLID;
     brush.lbColor = color;
     brush.lbHatch = 0;
-    pen_style |= _cairo_win32_line_cap (style->line_cap);
-    pen_style |= _cairo_win32_line_join (style->line_join);
+    if (!style->is_hairline) {
+	pen_style |= _cairo_win32_line_cap (style->line_cap);
+	pen_style |= _cairo_win32_line_join (style->line_join);
+    }
     pen = ExtCreatePen(pen_style,
-		       scale * style->line_width,
+		       style->is_hairline ? 1 : scale * style->line_width,
 		       &brush,
 		       style->num_dashes,
 		       dash_array);
diff --git a/test/Makefile.sources b/test/Makefile.sources
index b887b054d..12b9790b1 100644
--- a/test/Makefile.sources
+++ b/test/Makefile.sources
@@ -162,6 +162,7 @@ test_sources = \
 	group-paint.c					\
 	group-state.c					\
 	group-unaligned.c				\
+	hairline.c					\
 	half-coverage.c					\
 	halo.c						\
 	hatchings.c					\
diff --git a/test/hairline.c b/test/hairline.c
new file mode 100644
index 000000000..a14b944e3
--- /dev/null
+++ b/test/hairline.c
@@ -0,0 +1,175 @@
+#include "cairo-test.h"
+
+/**
+ * draw:
+ * @cr: a #cairo_t
+ * @width: Width of the test image
+ * @height: Height of the test image
+ * @scale_width: Percentage to scale the width
+ * @scale_height: Percentage to scale the height
+ * @fit_to_scale: Whether or not to adjust the image to fit in the scale parameters
+ * regardless of the scale parameters
+ * @correct_scale: Tests if the hairlines render correctly regardless of 
+ * whether or not the scale is set "correctly", as per
+ * https://cairographics.org/cookbook/ellipses/ 
+ */
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height, double scale_width, double scale_height, cairo_bool_t fit_to_scale, cairo_bool_t correct_scale)
+{
+	cairo_matrix_t save_matrix;
+	double fit_width = fit_to_scale ? scale_width : 1.0;
+	double fit_height = fit_to_scale ? scale_height : 1.0;
+	double fit_max = MAX(fit_width, fit_height);
+	double dash[] = {3.0};
+
+	if (cairo_get_hairline (cr) == TRUE) {
+		return CAIRO_TEST_ERROR;
+	}
+
+	/* Clear background */
+	cairo_set_source_rgb (cr, 1, 1, 1);
+	cairo_paint (cr);
+
+	cairo_set_source_rgb (cr, 0, 0, 0);
+	cairo_set_line_width (cr, 100.0); /* If everything is working right, this value should never get used */
+
+	/* Hairline sample */
+	if (correct_scale) {
+		cairo_get_matrix (cr, &save_matrix);
+	}
+	cairo_scale (cr, scale_width, scale_height);
+
+	cairo_set_hairline (cr, TRUE);
+	if (cairo_get_hairline (cr) == FALSE) {
+		return CAIRO_TEST_ERROR;
+	}
+
+	cairo_move_to (cr, 0, 0);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, width/fit_width/2, 0);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, 0, height/fit_height/2);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, width/fit_width/4, 0);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_arc (cr, width/fit_width/2, height/fit_height/2, width/fit_max/4, M_PI*0.5, M_PI*1.0);
+
+	if (correct_scale) {
+		cairo_set_matrix (cr, &save_matrix);
+	}
+	cairo_stroke (cr);
+
+	/* Dashed sample */
+	if (correct_scale) {
+		cairo_get_matrix (cr, &save_matrix);
+		cairo_scale (cr, scale_width, scale_height);
+	}
+	cairo_set_dash (cr, dash, 1, 0);
+	cairo_arc (cr, width/fit_width/2, height/fit_height/2, width/fit_max/4, M_PI*1.0, M_PI*1.5);
+	if (correct_scale) {
+		cairo_set_matrix (cr, &save_matrix);
+	}
+	cairo_stroke (cr);
+
+	/* Control sample */
+	if (correct_scale) {
+		cairo_get_matrix (cr, &save_matrix);
+		cairo_scale (cr, scale_width, scale_height);
+	}
+
+	cairo_set_line_width (cr, 3.0);
+	cairo_set_hairline (cr, FALSE);
+	if (cairo_get_hairline (cr) == TRUE) {
+		return CAIRO_TEST_ERROR;
+	}
+
+	cairo_set_dash (cr, 0, 0, 0);
+
+	cairo_move_to (cr, width/fit_width, height/fit_height);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, width/fit_width/2, height/fit_height);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, width/fit_width, height/fit_height/2);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_move_to (cr, width/fit_width*0.75, height/fit_height);
+	cairo_line_to (cr, width/fit_width/2, height/fit_height/2);
+	cairo_arc (cr, width/fit_width/2, height/fit_height/2, width/fit_max/4, M_PI*1.5, M_PI*2.0);
+
+	if (correct_scale) {
+		cairo_set_matrix (cr, &save_matrix);
+	}
+	cairo_stroke (cr);
+
+	/* Dashed sample */
+	if (correct_scale) {
+		cairo_get_matrix (cr, &save_matrix);
+		cairo_scale (cr, scale_width, scale_height);
+	}
+	cairo_set_dash (cr, dash, 1, 0);
+	cairo_arc (cr, width/fit_width/2, height/fit_height/2, width/fit_max/4, 0, M_PI*0.5);
+	if (correct_scale) {
+		cairo_set_matrix (cr, &save_matrix);
+	}
+	cairo_stroke (cr);
+
+	return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+draw_typical (cairo_t *cr, int width, int height)
+{
+	return draw (cr, width, height, 1.0, 1.0, TRUE, TRUE);
+}
+
+static cairo_test_status_t
+draw_scaled (cairo_t *cr, int width, int height)
+{
+	return draw (cr, width, height, 0.5, 0.5, FALSE, TRUE);
+}
+
+static cairo_test_status_t
+draw_anisotropic (cairo_t *cr, int width, int height)
+{
+	return draw (cr, width, height, 2.0, 5.0, TRUE, TRUE);
+}
+
+static cairo_test_status_t
+draw_anisotropic_incorrect (cairo_t *cr, int width, int height)
+{
+	return draw (cr, width, height, 2.0, 5.0, TRUE, FALSE);
+}
+
+CAIRO_TEST (hairline,
+		    "Tests hairlines are drawn at a single pixel width",
+			"path, stroke, hairline", /* keywords */
+			NULL, /* requirements */
+			49, 49,
+			NULL, draw_typical)
+
+CAIRO_TEST (hairline_big,
+		    "Tests hairlines are drawn at a single pixel width",
+			"path, stroke, hairline", /* keywords */
+			NULL, /* requirements */
+			99, 99,
+			NULL, draw_typical)
+
+CAIRO_TEST (hairline_scaled,
+		    "Tests hairlines are drawn at a single pixel width",
+			"path, stroke, hairline", /* keywords */
+			NULL, /* requirements */
+			99, 99,
+			NULL, draw_scaled)
+
+CAIRO_TEST (hairline_anisotropic,
+		    "Tests hairlines with a really lopsided scale parameter",
+			"path, stroke, hairline", /* keywords */
+			NULL, /* requirements */
+			99, 99,
+			NULL, draw_anisotropic)
+
+CAIRO_TEST (hairline_anisotropic_incorrect,
+		    "Tests hairlines with a really lopsided scale parameter",
+			"path, stroke, hairline", /* keywords */
+			NULL, /* requirements */
+			99, 99,
+			NULL, draw_anisotropic_incorrect)
\ No newline at end of file
diff --git a/test/meson.build b/test/meson.build
index 9d84d048d..ed8ec2cfd 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -162,6 +162,7 @@ test_sources = [
   'group-paint.c',
   'group-state.c',
   'group-unaligned.c',
+  'hairline.c',
   'half-coverage.c',
   'halo.c',
   'hatchings.c',
diff --git a/test/reference/hairline-anisotropic-incorrect.image16.ref.png b/test/reference/hairline-anisotropic-incorrect.image16.ref.png
new file mode 100644
index 000000000..c69a90d37
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.image16.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.pdf.ref.png b/test/reference/hairline-anisotropic-incorrect.pdf.ref.png
new file mode 100644
index 000000000..37acfaa55
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.pdf.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.quartz.ref.png b/test/reference/hairline-anisotropic-incorrect.quartz.ref.png
new file mode 100644
index 000000000..ec6ded3f3
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.quartz.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.ref.png b/test/reference/hairline-anisotropic-incorrect.ref.png
new file mode 100644
index 000000000..54929db44
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.svg11.argb32.ref.png b/test/reference/hairline-anisotropic-incorrect.svg11.argb32.ref.png
new file mode 100644
index 000000000..22b8cd51c
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.svg11.argb32.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.svg11.rgb24.ref.png b/test/reference/hairline-anisotropic-incorrect.svg11.rgb24.ref.png
new file mode 100644
index 000000000..d0f0f2e25
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.svg11.rgb24.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.xcb-window&.ref.png b/test/reference/hairline-anisotropic-incorrect.xcb-window&.ref.png
new file mode 100644
index 000000000..6c498ab46
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.xcb-window&.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.xcb-window.ref.png b/test/reference/hairline-anisotropic-incorrect.xcb-window.ref.png
new file mode 100644
index 000000000..6c498ab46
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.xcb-window.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.xcb.ref.png b/test/reference/hairline-anisotropic-incorrect.xcb.ref.png
new file mode 100644
index 000000000..6c498ab46
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.xcb.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.xlib-window.ref.png b/test/reference/hairline-anisotropic-incorrect.xlib-window.ref.png
new file mode 100644
index 000000000..042715325
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.xlib-window.ref.png differ
diff --git a/test/reference/hairline-anisotropic-incorrect.xlib.ref.png b/test/reference/hairline-anisotropic-incorrect.xlib.ref.png
new file mode 100644
index 000000000..042715325
Binary files /dev/null and b/test/reference/hairline-anisotropic-incorrect.xlib.ref.png differ
diff --git a/test/reference/hairline-anisotropic.image16.ref.png b/test/reference/hairline-anisotropic.image16.ref.png
new file mode 100644
index 000000000..a5a7217ca
Binary files /dev/null and b/test/reference/hairline-anisotropic.image16.ref.png differ
diff --git a/test/reference/hairline-anisotropic.pdf.ref.png b/test/reference/hairline-anisotropic.pdf.ref.png
new file mode 100644
index 000000000..acb2ffbcf
Binary files /dev/null and b/test/reference/hairline-anisotropic.pdf.ref.png differ
diff --git a/test/reference/hairline-anisotropic.quartz.ref.png b/test/reference/hairline-anisotropic.quartz.ref.png
new file mode 100644
index 000000000..ebf9fd64d
Binary files /dev/null and b/test/reference/hairline-anisotropic.quartz.ref.png differ
diff --git a/test/reference/hairline-anisotropic.ref.png b/test/reference/hairline-anisotropic.ref.png
new file mode 100644
index 000000000..bb3ba7793
Binary files /dev/null and b/test/reference/hairline-anisotropic.ref.png differ
diff --git a/test/reference/hairline-anisotropic.xcb-window&.ref.png b/test/reference/hairline-anisotropic.xcb-window&.ref.png
new file mode 100644
index 000000000..41a56f808
Binary files /dev/null and b/test/reference/hairline-anisotropic.xcb-window&.ref.png differ
diff --git a/test/reference/hairline-anisotropic.xcb-window.ref.png b/test/reference/hairline-anisotropic.xcb-window.ref.png
new file mode 100644
index 000000000..41a56f808
Binary files /dev/null and b/test/reference/hairline-anisotropic.xcb-window.ref.png differ
diff --git a/test/reference/hairline-anisotropic.xcb.ref.png b/test/reference/hairline-anisotropic.xcb.ref.png
new file mode 100644
index 000000000..41a56f808
Binary files /dev/null and b/test/reference/hairline-anisotropic.xcb.ref.png differ
diff --git a/test/reference/hairline-anisotropic.xlib-window.ref.png b/test/reference/hairline-anisotropic.xlib-window.ref.png
new file mode 100644
index 000000000..b84c2f449
Binary files /dev/null and b/test/reference/hairline-anisotropic.xlib-window.ref.png differ
diff --git a/test/reference/hairline-anisotropic.xlib.ref.png b/test/reference/hairline-anisotropic.xlib.ref.png
new file mode 100644
index 000000000..b84c2f449
Binary files /dev/null and b/test/reference/hairline-anisotropic.xlib.ref.png differ
diff --git a/test/reference/hairline-big.image16.ref.png b/test/reference/hairline-big.image16.ref.png
new file mode 100644
index 000000000..9d63e8e1b
Binary files /dev/null and b/test/reference/hairline-big.image16.ref.png differ
diff --git a/test/reference/hairline-big.pdf.ref.png b/test/reference/hairline-big.pdf.ref.png
new file mode 100644
index 000000000..6ed7e7ccf
Binary files /dev/null and b/test/reference/hairline-big.pdf.ref.png differ
diff --git a/test/reference/hairline-big.quartz.ref.png b/test/reference/hairline-big.quartz.ref.png
new file mode 100644
index 000000000..1f153dd70
Binary files /dev/null and b/test/reference/hairline-big.quartz.ref.png differ
diff --git a/test/reference/hairline-big.ref.png b/test/reference/hairline-big.ref.png
new file mode 100644
index 000000000..98ea2428f
Binary files /dev/null and b/test/reference/hairline-big.ref.png differ
diff --git a/test/reference/hairline-big.xcb-window&.ref.png b/test/reference/hairline-big.xcb-window&.ref.png
new file mode 100644
index 000000000..a662668db
Binary files /dev/null and b/test/reference/hairline-big.xcb-window&.ref.png differ
diff --git a/test/reference/hairline-big.xcb-window.ref.png b/test/reference/hairline-big.xcb-window.ref.png
new file mode 100644
index 000000000..a662668db
Binary files /dev/null and b/test/reference/hairline-big.xcb-window.ref.png differ
diff --git a/test/reference/hairline-big.xcb.ref.png b/test/reference/hairline-big.xcb.ref.png
new file mode 100644
index 000000000..a662668db
Binary files /dev/null and b/test/reference/hairline-big.xcb.ref.png differ
diff --git a/test/reference/hairline-big.xlib-window.ref.png b/test/reference/hairline-big.xlib-window.ref.png
new file mode 100644
index 000000000..ce6aaaeed
Binary files /dev/null and b/test/reference/hairline-big.xlib-window.ref.png differ
diff --git a/test/reference/hairline-big.xlib.ref.png b/test/reference/hairline-big.xlib.ref.png
new file mode 100644
index 000000000..ce6aaaeed
Binary files /dev/null and b/test/reference/hairline-big.xlib.ref.png differ
diff --git a/test/reference/hairline-scaled.image16.ref.png b/test/reference/hairline-scaled.image16.ref.png
new file mode 100644
index 000000000..5b82d0dc8
Binary files /dev/null and b/test/reference/hairline-scaled.image16.ref.png differ
diff --git a/test/reference/hairline-scaled.pdf.ref.png b/test/reference/hairline-scaled.pdf.ref.png
new file mode 100644
index 000000000..ef0973242
Binary files /dev/null and b/test/reference/hairline-scaled.pdf.ref.png differ
diff --git a/test/reference/hairline-scaled.quartz.ref.png b/test/reference/hairline-scaled.quartz.ref.png
new file mode 100644
index 000000000..c0653a12c
Binary files /dev/null and b/test/reference/hairline-scaled.quartz.ref.png differ
diff --git a/test/reference/hairline-scaled.ref.png b/test/reference/hairline-scaled.ref.png
new file mode 100644
index 000000000..f059b4288
Binary files /dev/null and b/test/reference/hairline-scaled.ref.png differ
diff --git a/test/reference/hairline-scaled.svg11.ref.png b/test/reference/hairline-scaled.svg11.ref.png
new file mode 100644
index 000000000..f45367bb0
Binary files /dev/null and b/test/reference/hairline-scaled.svg11.ref.png differ
diff --git a/test/reference/hairline-scaled.xcb-window&.ref.png b/test/reference/hairline-scaled.xcb-window&.ref.png
new file mode 100644
index 000000000..22ba5e291
Binary files /dev/null and b/test/reference/hairline-scaled.xcb-window&.ref.png differ
diff --git a/test/reference/hairline-scaled.xcb-window.ref.png b/test/reference/hairline-scaled.xcb-window.ref.png
new file mode 100644
index 000000000..22ba5e291
Binary files /dev/null and b/test/reference/hairline-scaled.xcb-window.ref.png differ
diff --git a/test/reference/hairline-scaled.xcb.ref.png b/test/reference/hairline-scaled.xcb.ref.png
new file mode 100644
index 000000000..22ba5e291
Binary files /dev/null and b/test/reference/hairline-scaled.xcb.ref.png differ
diff --git a/test/reference/hairline-scaled.xlib-window.ref.png b/test/reference/hairline-scaled.xlib-window.ref.png
new file mode 100644
index 000000000..a59338584
Binary files /dev/null and b/test/reference/hairline-scaled.xlib-window.ref.png differ
diff --git a/test/reference/hairline-scaled.xlib.ref.png b/test/reference/hairline-scaled.xlib.ref.png
new file mode 100644
index 000000000..a59338584
Binary files /dev/null and b/test/reference/hairline-scaled.xlib.ref.png differ
diff --git a/test/reference/hairline.image16.ref.png b/test/reference/hairline.image16.ref.png
new file mode 100644
index 000000000..fa3318cb6
Binary files /dev/null and b/test/reference/hairline.image16.ref.png differ
diff --git a/test/reference/hairline.pdf.ref.png b/test/reference/hairline.pdf.ref.png
new file mode 100644
index 000000000..0a0ecb33e
Binary files /dev/null and b/test/reference/hairline.pdf.ref.png differ
diff --git a/test/reference/hairline.quartz.ref.png b/test/reference/hairline.quartz.ref.png
new file mode 100644
index 000000000..e51ea63c9
Binary files /dev/null and b/test/reference/hairline.quartz.ref.png differ
diff --git a/test/reference/hairline.ref.png b/test/reference/hairline.ref.png
new file mode 100644
index 000000000..e33683115
Binary files /dev/null and b/test/reference/hairline.ref.png differ
diff --git a/test/reference/hairline.svg11.ref.png b/test/reference/hairline.svg11.ref.png
new file mode 100644
index 000000000..834f3a43a
Binary files /dev/null and b/test/reference/hairline.svg11.ref.png differ
diff --git a/test/reference/hairline.xcb-window&.ref.png b/test/reference/hairline.xcb-window&.ref.png
new file mode 100644
index 000000000..1175b14a9
Binary files /dev/null and b/test/reference/hairline.xcb-window&.ref.png differ
diff --git a/test/reference/hairline.xcb-window.ref.png b/test/reference/hairline.xcb-window.ref.png
new file mode 100644
index 000000000..1175b14a9
Binary files /dev/null and b/test/reference/hairline.xcb-window.ref.png differ
diff --git a/test/reference/hairline.xcb.ref.png b/test/reference/hairline.xcb.ref.png
new file mode 100644
index 000000000..1175b14a9
Binary files /dev/null and b/test/reference/hairline.xcb.ref.png differ
diff --git a/test/reference/hairline.xlib-window.ref.png b/test/reference/hairline.xlib-window.ref.png
new file mode 100644
index 000000000..ed55291f3
Binary files /dev/null and b/test/reference/hairline.xlib-window.ref.png differ
diff --git a/test/reference/hairline.xlib.ref.png b/test/reference/hairline.xlib.ref.png
new file mode 100644
index 000000000..ed55291f3
Binary files /dev/null and b/test/reference/hairline.xlib.ref.png differ
diff --git a/util/cairo-script/cairo-script-operators.c b/util/cairo-script/cairo-script-operators.c
index df8886ef6..d0452d338 100644
--- a/util/cairo-script/cairo-script-operators.c
+++ b/util/cairo-script/cairo-script-operators.c
@@ -5161,6 +5161,27 @@ _set_line_width (csi_t *ctx)
     return CSI_STATUS_SUCCESS;
 }
 
+static csi_status_t
+_set_hairline (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_bool_t set_hairline = FALSE; /* silence the compiler */
+
+    check (2);
+
+    status = _csi_ostack_get_boolean (ctx, 0, &set_hairline);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+	if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_hairline (cr, set_hairline);
+	pop (1);
+	return CSI_STATUS_SUCCESS;
+}
+
 static csi_status_t
 _set_matrix (csi_t *ctx)
 {
@@ -6625,6 +6646,7 @@ _defs[] = {
     { "set-line-cap", _set_line_cap },
     { "set-line-join", _set_line_join },
     { "set-line-width", _set_line_width },
+    { "set-hairline", _set_hairline },
     { "set-matrix", _set_matrix },
     { "set-miter-limit", _set_miter_limit },
     { "set-mime-data", _set_mime_data },


More information about the cairo-commit mailing list