|
| 1 | +//! Example code to show how to use pangocairo to render arbitrary shapes inside |
| 2 | +//! a text layout, positioned by Pango. |
| 3 | +//! https://gitlab.gnome.org/GNOME/pango/-/blob/master/examples/cairoshape.c |
| 4 | +const std = @import("std"); |
| 5 | +const log = std.log; |
| 6 | +const pi = std.math.pi; |
| 7 | +const cos = std.math.cos; |
| 8 | +const cairo = @import("cairo"); |
| 9 | +const pc = @import("pangocairo"); |
| 10 | + |
| 11 | +const BULLET = "•"; |
| 12 | + |
| 13 | +const text = |
| 14 | + \\The GNOME project provides two things: |
| 15 | + \\ |
| 16 | + \\ • The GNOME desktop environment |
| 17 | + \\ • The GNOME development platform |
| 18 | + \\ • Planet GNOME |
| 19 | +; |
| 20 | + |
| 21 | +const path = |
| 22 | + \\M 86.068,1 C 61.466,0 56.851,35.041 70.691,35.041 C 84.529,35.041 110.671,0 86.068,0 z |
| 23 | + \\M 45.217,30.699 C 52.586,31.149 60.671,2.577 46.821,4.374 C 32.976,6.171 37.845,30.249 45.217,30.699 z |
| 24 | + \\M 11.445,48.453 C 16.686,46.146 12.12,23.581 3.208,29.735 C -5.7,35.89 6.204,50.759 11.445,48.453 z |
| 25 | + \\M 26.212,36.642 C 32.451,35.37 32.793,9.778 21.667,14.369 C 10.539,18.961 19.978,37.916 26.212,36.642 L 26.212,36.642 z |
| 26 | + \\M 58.791,93.913 C 59.898,102.367 52.589,106.542 45.431,101.092 C 22.644,83.743 83.16,75.088 79.171,51.386 C 75.86,31.712 15.495,37.769 8.621,68.553 C 3.968,89.374 27.774,118.26 52.614,118.26 C 64.834,118.26 78.929,107.226 81.566,93.248 C 83.58,82.589 57.867,86.86 58.791,93.913 L 58.791,93.913 z |
| 27 | +; |
| 28 | + |
| 29 | +// I don't know if the multiline string works or I have to use a single string |
| 30 | +// const path = "M 86.068,1 C 61.466,0 56.851,35.041 70.691,35.041 C 84.529,35.041 110.671,0 86.068,0 z M 45.217,30.699 C 52.586,31.149 60.671,2.577 46.821,4.374 C 32.976,6.171 37.845,30.249 45.217,30.699 z M 11.445,48.453 C 16.686,46.146 12.12,23.581 3.208,29.735 C -5.7,35.89 6.204,50.759 11.445,48.453 z M 26.212,36.642 C 32.451,35.37 32.793,9.778 21.667,14.369 C 10.539,18.961 19.978,37.916 26.212,36.642 L 26.212,36.642 z M 58.791,93.913 C 59.898,102.367 52.589,106.542 45.431,101.092 C 22.644,83.743 83.16,75.088 79.171,51.386 C 75.86,31.712 15.495,37.769 8.621,68.553 C 3.968,89.374 27.774,118.26 52.614,118.26 C 64.834,118.26 78.929,107.226 81.566,93.248 C 83.58,82.589 57.867,86.86 58.791,93.913 L 58.791,93.913 z "; |
| 31 | + |
| 32 | +// TODO: add render method? (it would replace the miniSvgRender function) |
| 33 | +const MiniSvg = struct { |
| 34 | + width: f64, |
| 35 | + height: f64, |
| 36 | + path: []const u8, |
| 37 | +}; |
| 38 | + |
| 39 | +var gnome_foot_logo = MiniSvg{ |
| 40 | + .width = 96.2152, |
| 41 | + .height = 118.26, |
| 42 | + .path = path, |
| 43 | +}; |
| 44 | + |
| 45 | +// TODO: miniSvgRender |
| 46 | +fn miniSvgRender(shape: *MiniSvg, cr: ?cairo.CContext, do_path: c_int) void { |
| 47 | + log.debug("miniSvgRender", .{}); |
| 48 | + if (do_path == 1) { |
| 49 | + log.debug("do_path is TRUE", .{}); |
| 50 | + } else { |
| 51 | + log.debug("do_path is FALSE", .{}); |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +fn shapeRenderer(cr: ?cairo.CContext, attr: pc.PangoAttrShape, do_path: c_int, data: ?*c_void) callconv(.C) void { |
| 56 | + log.debug("shapeRenderer", .{}); |
| 57 | + if (do_path == 1) { |
| 58 | + log.debug("do_path is TRUE", .{}); |
| 59 | + } else { |
| 60 | + log.debug("do_path is FALSE", .{}); |
| 61 | + } |
| 62 | + // log.debug("attr {}", .{ attr }); |
| 63 | + // log.debug("PangoAttrShape {}", .{ attr.* }); |
| 64 | + log.debug("PangoAttrShape.data {}", .{attr.*.data}); |
| 65 | + // TODO: how to cast PangoAttrShape.data into *MiniSvg ? The alignment |
| 66 | + // doesn't match. |
| 67 | + // const shape = @ptrCast(*MiniSvg, attr.*.data.?); |
| 68 | + // const shape = (MiniSvg *) attr.data; // @ptrCast a MiniSvg ? |
| 69 | + |
| 70 | + log.debug("ink_rect.width {}", .{attr.*.ink_rect.width}); |
| 71 | + log.debug("ink_rect.height {}", .{attr.*.ink_rect.height}); |
| 72 | + |
| 73 | + // scale_x = (double) attr->ink_rect.width / (PANGO_SCALE * shape->width ); |
| 74 | + // scale_y = (double) attr->ink_rect.height / (PANGO_SCALE * shape->height); |
| 75 | + |
| 76 | + // cr.relMoveTo((double) attr->ink_rect.x / PANGO_SCALE, (double) attr->ink_rect.y / PANGO_SCALE); |
| 77 | + // cr.scale(scale_x, scale_y); |
| 78 | + // miniSvgRender(shape, cr, do_path); |
| 79 | + // this should probably be: |
| 80 | + // miniSvg.render(cr, do_path); |
| 81 | + |
| 82 | + // @panic("TODO: remove me when shapeRenderer is correct"); |
| 83 | +} |
| 84 | + |
| 85 | +// TODO: how to return a ?*c_void type? |
| 86 | +// fn pangoAttrDataCopyFunc(user_data: ?*const c_void) callconv(.C) ?*c_void { |
| 87 | +// log.debug("pangoAttrDataCopyFunc", .{}); |
| 88 | +// log.debug("user_data {}", .{user_data}); |
| 89 | +// return @ptrCast(?*c_void, user_data); |
| 90 | +// } |
| 91 | +const pangoAttrDataCopyFunc: ?pc.PangoAttrDataCopyFunc = null; |
| 92 | + |
| 93 | +fn gDestroyNotify(data: ?*c_void) callconv(.C) void { |
| 94 | + log.debug("gDestroyNotify", .{}); |
| 95 | + // log.debug("data {}", .{data.?.*}); // data.? is opaque |
| 96 | +} |
| 97 | +// const gDestroyNotify: ?pc.GDestroyNotify = null; |
| 98 | + |
| 99 | +fn getLayout(cr: *cairo.Context) !pc.Layout { |
| 100 | + var ink_rect = try pc.Rectangle.new(1 * pc.SCALE, -11 * pc.SCALE, 8 * pc.SCALE, 10 * pc.SCALE); |
| 101 | + var logical_rect = try pc.Rectangle.new(0 * pc.SCALE, -12 * pc.SCALE, 10 * pc.SCALE, 12 * pc.SCALE); |
| 102 | + |
| 103 | + var layout = try pc.Layout.create(cr); |
| 104 | + |
| 105 | + var ctx = try layout.getContext(); |
| 106 | + ctx.setShapeRenderer(shapeRenderer, null, null); |
| 107 | + layout.setText(text); |
| 108 | + |
| 109 | + var attrs = try pc.AttrList.new(); |
| 110 | + defer attrs.destroy(); |
| 111 | + |
| 112 | + var idx = std.mem.indexOf(u8, text[0..], BULLET); |
| 113 | + while (idx != null) { |
| 114 | + const str = text[idx.?..]; |
| 115 | + // log.debug("idx {} str.len {}", .{idx, str.len}); |
| 116 | + var attr = try pc.Attribute.newShapeWithData(MiniSvg, &ink_rect, &logical_rect, &gnome_foot_logo, pangoAttrDataCopyFunc, gDestroyNotify); |
| 117 | + // TODO: move this ptrCast and intCast to pango.zig |
| 118 | + // https://stackoverflow.com/questions/18659120/subtracting-two-strings-in-c |
| 119 | + const str_addr = @intCast(c_uint, @ptrToInt(str.ptr)); |
| 120 | + const text_addr = @intCast(c_uint, @ptrToInt(text)); |
| 121 | + attr.c_ptr.*.start_index = str_addr - text_addr; |
| 122 | + attr.c_ptr.*.end_index = attr.c_ptr.*.start_index + @intCast(c_uint, BULLET.len); |
| 123 | + attrs.insert(&attr); |
| 124 | + |
| 125 | + // find index for next iteration |
| 126 | + const i_rel = std.mem.indexOf(u8, text[idx.? + BULLET.len ..], BULLET); |
| 127 | + if (i_rel == null) { |
| 128 | + idx = null; |
| 129 | + } else { |
| 130 | + idx = idx.? + i_rel.? + 1; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + layout.setAttributes(&attrs); |
| 135 | + return layout; |
| 136 | +} |
| 137 | + |
| 138 | +fn drawText(cr: *cairo.Context, width: ?f64, height: ?f64) !pc.Size { |
| 139 | + log.info("=== drawText ===", .{}); |
| 140 | + var layout = try getLayout(cr); |
| 141 | + defer layout.destroy(); |
| 142 | + |
| 143 | + var size = pc.Size{ .width = 0.0, .height = 0.0 }; |
| 144 | + const margin = 10.0; |
| 145 | + |
| 146 | + if ((width != null) or (height != null)) { |
| 147 | + const original_size = layout.getPixelSize(); |
| 148 | + if (width != null) { |
| 149 | + size.width = original_size.width + 2.0 * margin; |
| 150 | + } |
| 151 | + if (height != null) { |
| 152 | + size.height = original_size.height + 2.0 * margin; |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + cr.moveTo(margin, margin); |
| 157 | + layout.show(cr); |
| 158 | + |
| 159 | + return size; |
| 160 | +} |
| 161 | + |
| 162 | +pub fn main() !void { |
| 163 | + // First create and use a 0x0 surface, to measure how large the final |
| 164 | + // surface needs to be. |
| 165 | + var surface = try cairo.Surface.image(0.0, 0.0); |
| 166 | + defer surface.destroy(); |
| 167 | + |
| 168 | + var cr = try cairo.Context.create(&surface); |
| 169 | + defer cr.destroy(); |
| 170 | + |
| 171 | + const size = try drawText(&cr, null, null); |
| 172 | + |
| 173 | + // TODO: Now create the final surface and draw to it. Reuse surface and cr? |
| 174 | + var surface2 = try cairo.Surface.image(@floatToInt(u16, size.width), @floatToInt(u16, size.height)); |
| 175 | + defer surface2.destroy(); |
| 176 | + |
| 177 | + var cr2 = try cairo.Context.create(&surface); |
| 178 | + defer cr2.destroy(); |
| 179 | + |
| 180 | + cr2.setSourceRgb(1.0, 1.0, 1.0); // white |
| 181 | + cr2.paint(); |
| 182 | + |
| 183 | + cr2.setSourceRgb(0.0, 0.0, 0.5); |
| 184 | + const size2 = try drawText(&cr2, size.width, size.height); |
| 185 | + |
| 186 | + _ = surface2.writeToPng("examples/generated/pango_shape.png"); |
| 187 | +} |
0 commit comments