# -*- encoding: utf-8 -*- # #-- # This file is part of HexaPDF. # # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby # Copyright (C) 2014-2017 Thomas Leitner # # HexaPDF is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License version 3 as # published by the Free Software Foundation with the addition of the # following permission added to Section 15 as permitted in Section 7(a): # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON # INFRINGEMENT OF THIRD PARTY RIGHTS. # # HexaPDF is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with HexaPDF. If not, see . # # The interactive user interfaces in modified source and object code # versions of HexaPDF must display Appropriate Legal Notices, as required # under Section 5 of the GNU Affero General Public License version 3. # # In accordance with Section 7(b) of the GNU Affero General Public # License, a covered work must retain the producer line in every PDF that # is created or manipulated using HexaPDF. #++ require 'hexapdf/error' module HexaPDF module Content # This module contains the color space implementations. # # == General Information # # The PDF specification defines several color spaces. Probably the most used ones are the # device color spaces DeviceRGB, DeviceCMYK and DeviceGray. However, there are several others. # For example, patterns are also implemented via color spaces. # # HexaPDF provides implementations for the most common color spaces. Additional ones can # easily be added. After implementing one it just has to be registered on the global # configuration object under the 'color_space.map' key. # # Color space implementations are currently used so that different colors can be # distinguished and to provide better error handling. # # # == Color Space Implementations # # A color space implementation consists of two classes: one for the color space and one for # its colors. # # The class for the color space needs to respond to the following methods: # # #initialize(definition):: # Creates the color space using the given array with the color space definition. The first # item in the array is always the color space family, the other items are color space # specific. # # #family:: # Returns the PDF name of the color space family this color space belongs to. # # #definition:: # Returns the color space definition as array. # # #default_color:: # Returns the default color for this color space. # # #color(*args):: # Returns the color corresponding to the given arguments. The number and types of the # arguments differ from one color space to another. # # The class representing a color in the color space needs to respond to the following methods: # # #color_space:: # Returns the associated color space object. # # #components:: # Returns an array of components that uniquely identifies this color within the color space. # # See: PDF1.7 s8.6 module ColorSpace # This module includes utility functions that are useful for all color classes. module ColorUtils # Normalizes the given color value so that it is in the range from 0.0 to 1.0. # # The conversion is done in the following way: # # * If the color value is an Integer, it is converted to a float and divided by +upper+. # * If the color value is greater than 1.0, it is set to 1.0. # * If the color value is less than 0.0, it is set to 0.0. def normalize_value(value, upper) value = value.to_f / upper if value.kind_of?(Integer) (value < 0 ? 0.0 : (value > 1 ? 1.0 : value)) end private :normalize_value # Compares this color to another one by looking at their associated color spaces and their # components. def ==(other) other.respond_to?(:components) && other.respond_to?(:color_space) && components == other.components && color_space == other.color_space end end # This class represents a "universal" color space that is used for all color spaces that # aren't implemented yet. class Universal # The color space definition used for creating this universal color space. attr_reader :definition # Creates the universal color space for the given color space definition. def initialize(definition) @definition = definition end # The default universal color. def default_color Color.new(self) end # Creates a new universal color object. The number of arguments isn't restricted. def color(*args) Color.new(self, *args) end # Returns the PDF color space family this color space belongs to. def family @definition[0] end # Compares this universal color space to another one by looking at their definitions. def ==(other) other.kind_of?(self.class) && definition == other.definition end # A single color in the universal color space. # # This doesn't represent a real color but is a place holder for a color in a color space # that isn't implemented yet. class Color include ColorUtils # Returns the specific Universal color space used for this color. attr_reader :color_space # Creates a new universal color with the given components. def initialize(color_space, *components) @color_space = color_space @components = components end # Returns the componets of the universal color, i.e. all arguments provided on # initialization. def components @components end end end # The DeviceRGB color space. class DeviceRGB # The one (and only) DeviceRGB color space. DEFAULT = new # Returns the DeviceRGB color space object. def self.new DEFAULT end # Returns the default color for the DeviceRGB color space. def default_color Color.new(0.0, 0.0, 0.0) end # Returns the color object for the given red, green and blue components. def color(r, g, b) Color.new(r, g, b) end # Returns +:DeviceRGB+. def family :DeviceRGB end # A color in the DeviceRGB color space. # # Color values can either be integers in the range from 0 to 255 or floating point numbers # between 0.0 and 1.0. The integer color values are automatically normalized to the # DeviceRGB color value range of 0.0 to 1.0. # # See: PDF1.7 s8.6.4.3 class Color include ColorUtils # Initializes the color with the +r+ (red), +g+ (green) and +b+ (blue) components. # # Each argument has to be either an integer between 0 and 255 or a float between 0.0 and # 1.0. def initialize(r, g, b) @r = normalize_value(r, 255) @g = normalize_value(g, 255) @b = normalize_value(b, 255) end # Returns the DeviceRGB color space module. def color_space DeviceRGB::DEFAULT end # Returns the RGB color as an array of normalized color values. def components [@r, @g, @b] end end end # The DeviceCMYK color space. class DeviceCMYK # The one (and only) DeviceCMYK color space. DEFAULT = new # Returns the DeviceCMYK color space object. def self.new DEFAULT end # Returns the default color for the DeviceCMYK color space. def default_color Color.new(0.0, 0.0, 0.0, 1.0) end # Returns the color object for the given cyan, magenta, yellow and black components. def color(c, m, y, k) Color.new(c, m, y, k) end # Returns +:DeviceCMYK+. def family :DeviceCMYK end # A color in the DeviceCMYK color space. # # Color values can either be integers in the range from 0 to 100 or floating point numbers # between 0.0 and 1.0. The integer color values are automatically normalized to the # DeviceCMYK color value range of 0.0 to 1.0. # # See: PDF1.7 s8.6.4.4 class Color include ColorUtils # Initializes the color with the +c+ (cyan), +m+ (magenta), +y+ (yellow) and +k+ (black) # components. # # Each argument has to be either an integer between 0 and 100 or a float between 0.0 and # 1.0. def initialize(c, m, y, k) @c = normalize_value(c, 100) @m = normalize_value(m, 100) @y = normalize_value(y, 100) @k = normalize_value(k, 100) end # Returns the DeviceCMYK color space module. def color_space DeviceCMYK::DEFAULT end # Returns the CMYK color as an array of normalized color values. def components [@c, @m, @y, @k] end end end # The DeviceGray color space. class DeviceGray # The one (and only) DeviceGray color space. DEFAULT = new # Returns the DeviceGray color space object. def self.new DEFAULT end # Returns the default color for the DeviceGray color space. def default_color Color.new(0.0) end # Returns the color object for the given gray component. def color(gray) Color.new(gray) end # Returns +:DeviceGray+. def family :DeviceGray end # A color in the DeviceGray color space. # # Color values can either be integers in the range from 0 to 255 or floating point numbers # between 0.0 and 1.0. The integer color values are automatically normalized to the # DeviceGray color value range of 0.0 to 1.0. # # See: PDF1.7 s8.6.4.2 class Color include ColorUtils # Initializes the color with the +gray+ component. # # The argument +gray+ has to be either an integer between 0 and 255 or a float between # 0.0 and 1.0. def initialize(gray) @gray = normalize_value(gray, 255) end # Returns the DeviceGray color space module. def color_space DeviceGray::DEFAULT end # Returns the normalized gray value as an array. def components [@gray] end end end end end end