Module | PDF::Writer::Graphics |
In: |
lib/pdf/writer/graphics.rb
|
This module contains graphics primitives. Objects that include this module must respond to add_content.
The PDF::Writer coordinate system is in PDF userspace units. The coordinate system in PDF::Writer is slightly different than might be expected, in that (0, 0) is at the lower left-hand corner of the canvas (page), not the normal top left-hand corner of the canvas. (See the diagram below.)
Y Y 0+-----+X | | | | | | 0+-----+X 0 0
Each primitive provided below indicates the New Point, or the coordinates new drawing point at the completion of the drawing operation. Drawing operations themselves do not draw or fill the path. This must be done by one of the stroke or fill operators, stroke, close_stroke, fill, close_fill, fill_stroke, or close_fill_stroke.
Drawing operations return self (the canvas) so that operations may be chained.
KAPPA | = | 4.0 * ((Math.sqrt(2) - 1.0) / 3.0) | This constant is used to approximate a symmetrical arc using a cubic Bezier curve. |
Add an image from a loaded image (JPEG or PNG) resource at position (x, y) (the lower left-hand corner of the image) and scaled to width by height units. If provided, image_info is a PDF::Writer::Graphics::ImageInfo object.
In PDF::Writer 1.1 or later, the new link parameter is a hash with two keys:
:type: | The type of link, either :internal or :external. |
:target: | The destination of the link. For an :internal link, this is an internal cross-reference destination. For an :external link, this is an URI. |
This will automatically make the image a clickable link if set.
# File lib/pdf/writer/graphics.rb, line 568 568: def add_image(image, x, y, width = nil, height = nil, image_info = nil, link = nil) 569: if image.kind_of?(PDF::Writer::External::Image) 570: label = image.label 571: image_obj = image 572: image_info ||= image.image_info 573: else 574: image_info ||= PDF::Writer::Graphics::ImageInfo.new(image) 575: 576: tt = Time.now 577: @images << tt 578: id = @images.index(tt) 579: label = "I#{id}" 580: image_obj = PDF::Writer::External::Image.new(self, image, image_info, label) 581: @images[id] = image_obj 582: end 583: 584: if width.nil? and height.nil? 585: width = image_info.width 586: height = image_info.height 587: end 588: 589: width ||= height / image_info.height.to_f * image_info.width 590: height ||= width * image_info.height / image_info.width.to_f 591: 592: tt = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ" 593: add_content(tt % [ width, height, x, y, label ]) 594: 595: if link 596: case link[:type] 597: when :internal 598: add_internal_link(link[:target], x, y, x + width, y + height) 599: when :external 600: add_link(link[:target], x, y, x + width, y + height) 601: end 602: end 603: 604: image_obj 605: end
Add an image from a file to the current page at position (x, y) (the lower left-hand corner of the image). The image will be scaled to width by height units. The image may be a PNG or JPEG image.
The image parameter may be a filename or an object that returns the full image data when read is called with no parameters (such as an IO object). If ‘open-uri’ is loaded, then the image name may be an URI.
In PDF::Writer 1.1 or later, the new link parameter is a hash with two keys:
:type: | The type of link, either :internal or :external. |
:target: | The destination of the link. For an :internal link, this is an internal cross-reference destination. For an :external link, this is an URI. |
This will automatically make the image a clickable link if set.
# File lib/pdf/writer/graphics.rb, line 540 540: def add_image_from_file(image, x, y, width = nil, height = nil, link = nil) 541: data = nil 542: 543: if image.respond_to?(:read) 544: data = image.read 545: else 546: open(image, 'rb') { |ff| data = ff.read } 547: end 548: 549: add_image(data, x, y, width, height, nil, link) 550: end
Draws a circle of radius r with the centre-point at (x, y) as a complete subpath. The drawing point will be moved to the centre-point upon completion of the drawing the circle.
# File lib/pdf/writer/graphics.rb, line 227 227: def circle_at(x, y, r) 228: ellipse_at(x, y, r, r) 229: end
Close the current path by appending a straight line segment from the drawing point to the starting point of the path. If the path is closed, this does nothing. This operator terminates the current subpath.
# File lib/pdf/writer/graphics.rb, line 50 50: def close 51: add_content(" h") 52: self 53: end
Close the current path by appending a straight line segment from the drawing point to the starting point of the path, and then fill it. This does the same as close followed by fill.
See fill for more information on fill rules.
# File lib/pdf/writer/graphics.rb, line 92 92: def close_fill(rule = nil) 93: close 94: fill(rule) 95: self 96: end
Closes, fills and then strokes the path. Open subpaths are explicitly closed before being filled (as if close and then fill_stroke had been called). This is the same as constructing two identical path objects, calling fill on one and stroke on the other. Paths filled and stroked in this manner are treated as if they were one object for PDF transparency purposes (PDF transparency is not yet supported by PDF::Writer).
See fill for more information on fill rules.
# File lib/pdf/writer/graphics.rb, line 124 124: def close_fill_stroke(rule = nil) 125: if :even_odd == rule 126: add_content(" b*") 127: else 128: add_content(" b") 129: end 130: self 131: end
Draws a cubic Bezier curve from the drawing point to (x2, y2) using (x0, y0) and (x1, y1) as the control points for the curve.
New Point: | (x2, y2) |
Subpath: | Current |
# File lib/pdf/writer/graphics.rb, line 157 157: def curve_to(x0, y0, x1, y1, x2, y2) 158: add_content("\n%.3f %.3f %.3f %.3f %.3f %.3f c" % [ x0, y0, x1, y1, x2, y2 ]) 159: self 160: end
Draws a cubic Bezier curve from the drawing point to (x1, y1) using (x0, y0) and (x1, y1) as the control points for the curve.
New Point: | (x1, y1) |
Subpath: | Current |
# File lib/pdf/writer/graphics.rb, line 179 179: def ecurve_to(x0, y0, x1, y1) 180: add_content("\n%.3f %.3f %.3f %.3f y" % [ x0, y0, x1, y1 ]) 181: self 182: end
Draw an ellipse centered at (x, y) with x radius r1 and y radius r2. A partial ellipse can be drawn by specifying the starting and finishing angles.
New Point: | (x, y) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 256 256: def ellipse2_at(x, y, r1, r2 = r1, start = 0, stop = 359.99, segments = 8) 257: segments = 2 if segments < 2 258: 259: start = PDF::Math.deg2rad(start) 260: stop = PDF::Math.deg2rad(stop) 261: 262: arc = stop - start 263: segarc = arc / segments.to_f 264: dtm = segarc / 3.0 265: 266: theta = start 267: a0 = x + r1 * Math.cos(theta) 268: b0 = y + r2 * Math.sin(theta) 269: c0 = -r1 * Math.sin(theta) 270: d0 = r2 * Math.cos(theta) 271: 272: move_to(a0, b0) 273: 274: (1..segments).each do |ii| 275: theta = ii * segarc + start 276: 277: a1 = x + r1 * Math.cos(theta) 278: b1 = y + r2 * Math.sin(theta) 279: c1 = -r1 * Math.sin(theta) 280: d1 = r2 * Math.cos(theta) 281: 282: curve_to(a0 + (c0 * dtm), 283: b0 + (d0 * dtm), 284: a1 - (c1 * dtm), 285: b1 - (d1 * dtm), a1, b1) 286: 287: a0 = a1 288: b0 = b1 289: c0 = c1 290: d0 = d1 291: end 292: 293: move_to(x, y) 294: self 295: end
Draws an ellipse of x radius r1 and y radius r2 with the centre-point at (x, y) as a complete subpath. The drawing point will be moved to the centre-point upon completion of the drawing the ellipse.
# File lib/pdf/writer/graphics.rb, line 235 235: def ellipse_at(x, y, r1, r2 = r1) 236: l1 = r1 * KAPPA 237: l2 = r2 * KAPPA 238: move_to(x + r1, y) 239: # Upper right hand corner 240: curve_to(x + r1, y + l1, x + l2, y + r2, x, y + r2) 241: # Upper left hand corner 242: curve_to(x - l2, y + r2, x - r1, y + l1, x - r1, y) 243: # Lower left hand corner 244: curve_to(x - r1, y - l1, x - l2, y - r2, x, y - r2) 245: # Lower right hand corner 246: curve_to(x + l2, y - r2, x + r1, y - l1, x + r1, y) 247: move_to(x, y) 248: end
Fills the path. Open subpaths are implicitly closed before being filled. PDF offers two methods for determining the fill region. The first is called the "nonzero winding number" and is the default fill. The second is called "even-odd".
Use the even-odd rule (called with fill(:even_odd)) with caution, as this will cause certain portions of the path to be considered outside of the fill region, resulting in interesting cutout patterns.
# File lib/pdf/writer/graphics.rb, line 78 78: def fill(rule = nil) 79: if :even_odd == rule 80: add_content(" f*") 81: else 82: add_content(" f") 83: end 84: self 85: end
Forces the color for fill operations to be set, even if the color is the same as the current color. Does nothing if nil is provided.
# File lib/pdf/writer/graphics.rb, line 489 489: def fill_color!(color = nil) 490: if color 491: @current_fill_color = color 492: add_content "\n#{@current_fill_color.pdf_fill}" 493: end 494: end
Fills and then strokes the path. Open subpaths are implicitly closed before being filled. This is the same as constructing two identical path objects, calling fill on one and stroke on the other. Paths filled and stroked in this manner are treated as if they were one object for PDF transparency purposes (the PDF transparency model is not yet supported by PDF::Writer).
See fill for more information on fill rules.
# File lib/pdf/writer/graphics.rb, line 106 106: def fill_stroke(rule = nil) 107: if :even_odd == rule 108: add_content(" B*") 109: else 110: add_content(" B") 111: end 112: self 113: end
Add an image easily to a PDF document. image is the name of a JPG or PNG image. options is a Hash:
:pad: | The number of PDF userspace units that will be on all sides of the image. The default is 5 units. |
:width: | The desired width of the image. The image will be resized to this width with the aspect ratio kept. If unspecified, the image‘s natural width will be used. |
:resize: | How to resize the image, either :width (resizes the image to be as wide as the margins) or :full (resizes the image to be as large as possible). May be a numeric value, used as a multiplier for the image size (e.g., 0.5 will shrink the image to half-sized). If this and :width are unspecified, the image‘s natural size will be used. Mutually exclusive with the <tt>:width<tt> option. |
:justification: | The placement of the image. May be :center, :right, or :left. Defaults to :left. |
:border: | The border options. No default border. If specified, must be either true, which uses the default border, or a Hash. |
:link: | Makes the image a clickable link. |
Image borders are specified as a hash with two options:
:color: | The colour of the border. Defaults to 50% grey. |
:style: | The stroke style of the border. This must be a StrokeStyle object and defaults to the default line. |
Image links are defined as a hash with two options:
:type: | The type of link, either :internal or :external. |
:target: | The destination of the link. For an :internal link, this is an internal cross-reference destination. For an :external link, this is an URI. |
# File lib/pdf/writer/graphics.rb, line 648 648: def image(image, options = {}) 649: width = options[:width] 650: pad = options[:pad] || 5 651: resize = options[:resize] 652: just = options[:justification] || :left 653: border = options[:border] 654: link = options[:link] 655: 656: if image.kind_of?(PDF::Writer::External::Image) 657: info = image.image_info 658: image_data = image 659: else 660: if image.respond_to?(:read) 661: image_data = image.read 662: else 663: image_data = open(image, "rb") { |file| file.read } 664: end 665: info = PDF::Writer::Graphics::ImageInfo.new(image_data) 666: end 667: 668: raise "Unsupported Image Type" unless %w(JPEG PNG).include?(info.format) 669: 670: width = info.width if width.nil? 671: aspect = info.width.to_f / info.height.to_f 672: 673: # Get the maximum width of the image on insertion. 674: if @columns_on 675: max_width = @columns[:width] - (pad * 2) 676: else 677: max_width = @page_width - (pad * 2) - @left_margin - @right_margin 678: end 679: 680: if resize == :full or resize == :width or width > max_width 681: width = max_width 682: end 683: 684: # Keep the height in an appropriate aspect ratio of the width. 685: height = (width / aspect.to_f) 686: 687: # Resize the image. 688: if resize.kind_of?(Numeric) 689: width *= resize 690: height *= resize 691: end 692: 693: # Resize the image *again*, if it is wider than what is available. 694: if width > max_width 695: height = (width / aspect.to_f) 696: end 697: 698: # If the height is greater than the available space: 699: havail = @y - @bottom_margin - (pad * 2) 700: if height > havail 701: # If the image is to be resized to :full (remaining space 702: # available), adjust the image size appropriately. Otherwise, start 703: # a new page and flow to the next page. 704: if resize == :full 705: height = havail 706: width = (height * aspect) 707: else 708: start_new_page 709: end 710: end 711: 712: # Find the x and y positions. 713: y = @y - pad - height 714: x = @left_margin + pad 715: 716: if (width < max_width) 717: case just 718: when :center 719: x += (max_width - width) / 2.0 720: when :right 721: x += (max_width - width) 722: end 723: end 724: 725: image_obj = add_image(image_data, x, y, width, height, info) 726: 727: if border 728: border = {} if true == border 729: border[:color] ||= Color::RGB::Grey50 730: border[:style] ||= PDF::Writer::StrokeStyle::DEFAULT 731: 732: save_state 733: stroke_color border[:color] 734: stroke_style border[:style] 735: rectangle(x, y - pad, width, height - pad).stroke 736: restore_state 737: end 738: 739: if link 740: case link[:type] 741: when :internal 742: add_internal_link(link[:target], x, y - pad, x + width, y + height - pad) 743: when :external 744: add_link(link[:target], x, y - pad, x + width, y + height - pad) 745: end 746: end 747: 748: @y = @y - pad - height 749: 750: image_obj 751: end
Move the drawing point to the specified coordinates (x, y).
New Point: | (x, y) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 137 137: def move_to(x, y) 138: add_content("\n%.3f %.3f m" % [ x, y ]) 139: self 140: end
Draw a polygon. points is an array of PolygonPoint objects, or an array that can be converted to an array of PolygonPoint objects with PDF::Writer::PolygonPoint.new(*value).
New Point: | (points[-1].x, points[-1].y) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 325 325: def polygon(points) 326: points = points.map { |pp| 327: pp.kind_of?(Array) ? PDF::Writer::PolygonPoint.new(*pp) : pp 328: } 329: 330: point = points.shift 331: 332: move_to(point.x, point.y) 333: 334: while not points.empty? 335: point = points.shift 336: 337: case point.connector 338: when :curve 339: c1 = point 340: c2 = points.shift 341: point = points.shift 342: 343: curve_to(c1.x, c1.y, c2.x, c2.y, point.x, point.y) 344: when :scurve 345: c1 = point 346: point = points.shift 347: scurve_to(c1.x, c1.y, point.x, point.y) 348: when :ecurve 349: c1 = point 350: point = points.shift 351: ecurve_to(c1.x, c1.y, point.x, point.y) 352: else 353: line_to(point.x, point.y) 354: end 355: end 356: 357: self 358: end
Rotate the axis of the coordinate system by the specified clockwise angle.
# File lib/pdf/writer/graphics.rb, line 762 762: def rotate_axis(angle) 763: rad = PDF::Math.deg2rad(angle) 764: tt = "\n%.3f %.3f %.3f %.3f 0 0 cm" 765: tx = [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad) ] 766: add_content(tt % tx) 767: self 768: end
Draw a rounded rectangle with corners (x, y) and (x + w, y - h) and corner radius r. The radius should be significantly smaller than h and w.
New Point: | (x + w, y - h) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 376 376: def rounded_rectangle(x, y, w, h, r) 377: x1 = x 378: x2 = x1 + w 379: y1 = y 380: y2 = y1 - h 381: 382: r1 = r 383: r2 = r / 2.0 384: 385: points = [ 386: [ x1 + r1, y1, :line ], 387: [ x2 - r1, y1, :line ], 388: [ x2 - r2, y1, :curve ], # cp1 389: [ x2, y1 - r2, ], # cp2 390: [ x2, y1 - r1, ], # ep 391: [ x2, y2 + r1, :line ], 392: [ x2, y2 + r2, :curve ], # cp1 393: [ x2 - r2, y2, ], # cp2 394: [ x2 - r1, y2, ], # ep 395: [ x1 + r1, y2, :line ], 396: [ x1 + r2, y2, :curve ], # cp1 397: [ x1, y2 + r2, ], # cp2 398: [ x1, y2 + r1, ], # ep 399: [ x1, y1 - r1, :line ], 400: [ x1, y1 - r2, :curve ], # cp1 401: [ x1 + r2, y1, ], # cp2 402: [ x1 + r1, y1, ], # ep 403: ] 404: polygon(points) 405: move_to(x2, y2) 406: self 407: end
Scale the coordinate system axis by the specified factors.
# File lib/pdf/writer/graphics.rb, line 771 771: def scale_axis(x = 1, y = 1) 772: add_content("\n%.3f 0 0 %.3f 0 0 cm" % [ x, y ]) 773: self 774: end
Draws a cubic Bezier curve from the drawing point to (x1, y1) using the drawing point and (x0, y0) as the control points for the curve.
New Point: | (x1, y1) |
Subpath: | Current |
# File lib/pdf/writer/graphics.rb, line 168 168: def scurve_to(x0, y0, x1, y1) 169: add_content("\n%.3f %.3f %.3f %.3f v" % [ x0, y0, x1, y1 ]) 170: self 171: end
Draws an ellipse segment. Draws a closed partial ellipse.
New Point: | (x, y) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 301 301: def segment_at(x, y, r1, r2 = r1, start = 0, stop = 360, segments = 8) 302: ellipse2_at(x, y, r1, r2, start, stop, segments) 303: 304: start = PDF::Math.deg2rad(start) 305: stop = PDF::Math.deg2rad(stop) 306: 307: ax = x + r1 * Math.cos(start) 308: ay = y + r2 * Math.sin(start) 309: bx = x + r1 * Math.cos(stop) 310: by = y + r2 * Math.sin(stop) 311: 312: move_to(ax, ay) 313: line_to(x, y) 314: line_to(bx, by) 315: move_to(x, y) 316: self 317: end
Skew the coordinate system axis by the specified angles.
# File lib/pdf/writer/graphics.rb, line 777 777: def skew_axis(xangle = 0, yangle = 0) 778: xr = PDF::Math.deg2rad(xangle) 779: yr = PDF::Math.deg2rad(yangle) 780: 781: xr = Math.tan(xr) if xangle != 0 782: yr = Math.tan(yr) if yangle != 0 783: 784: add_content("\n1 %.3f %.3f 1 0 0 cm" % [ xr, yr ]) 785: self 786: end
Draws a star centered on (x, y) with rays portions of length from the centre. Stars with an odd number of rays should have the top ray pointing toward the top of the document. This will not create a "star" with fewer than four points.
New Point: | (cx, cy) |
Subpath: | New |
# File lib/pdf/writer/graphics.rb, line 416 416: def star(cx, cy, length, rays = 5) 417: rays = 4 if rays < 4 418: points = [] 419: part = Math::PI / rays.to_f 420: 421: 0.step((rays * 4), 2) do |ray| 422: if ((ray / 2) % 2 == 0) 423: dist = length / 2.0 424: else 425: dist = length 426: end 427: 428: x = cx + Math.cos((1.5 + ray / 2.0) * part) * dist 429: y = cy + Math.sin((1.5 + ray / 2.0) * part) * dist 430: points << [ x, y ] 431: end 432: 433: polygon(points) 434: move_to(cx, cy) 435: self 436: end
Stroke the path. This operation terminates a path object and draws it.
# File lib/pdf/writer/graphics.rb, line 56 56: def stroke 57: add_content(" S") 58: self 59: end
Forces the color for stroke operations to be set, even if the color is the same as the current color. Does nothing if nil is provided.
# File lib/pdf/writer/graphics.rb, line 508 508: def stroke_color!(color = nil) 509: if color 510: @current_stroke_color = color 511: add_content "\n#{@current_stroke_color.pdf_stroke}" 512: end 513: end
This sets the line drawing style. This must be a PDF::Writer::StrokeStyle object.
# File lib/pdf/writer/graphics.rb, line 440 440: def stroke_style(style) 441: stroke_style!(style) if @current_stroke_style.nil? or style != @current_stroke_style 442: end
Forces the line drawing style to be set, even if it‘s the same as the current color. Emits the current stroke style if nil is provided.
# File lib/pdf/writer/graphics.rb, line 446 446: def stroke_style!(style = nil) 447: @current_stroke_style = style if style 448: add_content "\n#{@current_stroke_style.render}" if @current_stroke_style 449: end
Set the text rendering style. This may be one of the following options:
0: | fill |
1: | stroke |
2: | fill then stroke |
3: | invisible |
4: | fill and add to clipping path |
5: | stroke and add to clipping path |
6: | fill and stroke and add to clipping path |
7: | add to clipping path |
# File lib/pdf/writer/graphics.rb, line 467 467: def text_render_style(style) 468: text_render_style!(style) unless @current_text_render_style and style == @current_text_render_style 469: end
Forces the text rendering style to be set, even if it‘s the same as the current style.
# File lib/pdf/writer/graphics.rb, line 473 473: def text_render_style!(style) 474: @current_text_render_style = style 475: end
Reutnrs the current text rendering style.
# File lib/pdf/writer/graphics.rb, line 478 478: def text_render_style? 479: @current_text_render_style 480: end
Transforms the coordinate axis with the appended matrix. All transformations (including those above) are performed with this matrix. The transformation matrix is:
+- -+ | a c e | | b d f | | 0 0 1 | +- -+
The six values are represented as a six-digit vector: [ a b c d e f ]
# File lib/pdf/writer/graphics.rb, line 810 810: def transform_matrix(a, b, c, d, e, f) 811: add_content("\n%.3f %.3f %.3f %.3f %.3f %.3f cm" % [ a, b, c, d, e, f ]) 812: end