# This module provides an interface to the top level bits of libvips # via ruby-ffi. # # Author:: John Cupitt (mailto:jcupitt@gmail.com) # License:: MIT require "ffi" module Vips if Vips.at_least_libvips?(8, 9) attach_function :vips_source_custom_new, [], :pointer end # A source you can attach action signal handlers to to implement # custom input types. # # For example: # # ```ruby # file = File.open "some/file/name", "rb" # source = Vips::SourceCustom.new # source.on_read { |length| file.read length } # image = Vips::Image.new_from_source source # ``` # # (just an example -- of course in practice you'd use {Source#new_from_file} # to read from a named file) class SourceCustom < Vips::Source module SourceCustomLayout def self.included(base) base.class_eval do layout :parent, Vips::Source::Struct # rest opaque end end end class Struct < Vips::Source::Struct include SourceCustomLayout end class ManagedStruct < Vips::Source::ManagedStruct include SourceCustomLayout end def initialize pointer = Vips.vips_source_custom_new raise Vips::Error if pointer.null? super(pointer) end # The block is executed to read data from the source. The interface is # exactly as IO::read, ie. it takes a maximum number of bytes to read and # returns a string of bytes from the source, or nil if the source is already # at end of file. # # @yieldparam length [Integer] Read and return up to this many bytes # @yieldreturn [String] Up to length bytes of data, or nil for EOF def on_read &block signal_connect "read" do |buf, len| chunk = block.call len return 0 if chunk.nil? bytes_read = chunk.bytesize buf.put_bytes(0, chunk, 0, bytes_read) chunk.clear bytes_read end end # The block is executed to seek the source. The interface is exactly as # IO::seek, ie. it should take an offset and whence, and return the # new read position. # # This handler is optional -- if you do not attach a seek handler, # {Source} will treat your source like an unseekable pipe object and # do extra caching. # # @yieldparam offset [Integer] Seek offset # @yieldparam whence [Integer] Seek whence # @yieldreturn [Integer] the new read position, or -1 on error def on_seek &block signal_connect "seek" do |offset, whence| block.call offset, whence end end end end