subroutine gtx_pen(output,ipen,userpens)
  use gbl_message
  use gtv_interfaces, except_this=>gtx_pen
  use gtv_protocol
  use gtv_types
  !---------------------------------------------------------------------
  ! @ private
  !   Set the color to be used by the forthcoming segments (lines,
  ! polygons, but not images).
  !---------------------------------------------------------------------
  type(gt_display), intent(inout) :: output    ! Output instance
  integer(kind=4),  intent(in)    :: ipen      !
  type(gt_lut),     intent(in)    :: userpens  ! Set of user pens (PEN /LUT)
  ! Local
  character(len=*), parameter :: rname='PEN'
  integer(kind=4) :: r,g,b
  logical :: do_neg
  !
  ! Resolve ipen to RGB:
  call gtv_pencol_id2rgb(output,userpens,ipen,r,g,b,do_neg)
  !
  select case(output%dev%protocol)
  case (p_null)
    continue
  case (p_x)
    if (do_neg) then
      call x_pen_invert(output%x%graph_env)
    else
      call x_pen_rgb(output%x%graph_env,r,g,b)
    endif
  case (p_postscript)
    call ps_pen_rgb(r,g,b)
  case (p_svg)
    call svg_pen_rgb(r,g,b)
  case (p_png)
    if (do_neg) then
      call png_pen_negative(output)
    else
      call png_pen_rgb(output,r,g,b)
    endif
  case default
    call gtv_message(seve%e,rname,'Internal error: unknown protocol')
  end select
  !
end subroutine gtx_pen
!
subroutine gtv_pencol_id2rgb(output,userpens,ipen,r,g,b,do_neg)
  use gbl_message
  use gtv_interfaces, except_this=>gtv_pencol_id2rgb
  use gtv_colors
  use gtv_plot
  use gtv_types
  !---------------------------------------------------------------------
  ! @ private
  ! Convert a pen identifier (number) into RGB values (range [0:255]).
  ! Be careful this can be context dependent (e.g. foreground/background
  ! colors).
  !---------------------------------------------------------------------
  type(gt_display), intent(in)  :: output    ! Output instance
  type(gt_lut),     intent(in)  :: userpens  ! Set of user pens (PEN /LUT)
  integer(kind=4),  intent(in)  :: ipen      !
  integer(kind=4),  intent(out) :: r,g,b     !
  logical,          intent(out) :: do_neg    !
  ! Local
  character(len=*), parameter :: rname='PEN'
  integer(kind=4) :: jpen
  !
  ! In case of error:
  r = 0
  g = 0
  b = 0
  !
  do_neg = ipen.eq.pen_color_negative
  if (do_neg) then
    ! Prepare a foreground pen for devices which do not support negative
    ! colors
    jpen = pen_color_foreground
  else
    jpen = ipen
  endif
  !
  if (jpen.eq.pen_color_background) then
    if (output%background.eq.0) then  ! Black
      jpen = pen_color_black
    else
      jpen = pen_color_white
    endif
  elseif (jpen.eq.pen_color_foreground) then
    if (output%background.eq.0) then  ! Black
      jpen = pen_color_white
    else
      jpen = pen_color_black
    endif
  endif
  !
  if (jpen.gt.0) then
    ! A standard RGB color
    r = X11_rgb(1,jpen)
    g = X11_rgb(2,jpen)
    b = X11_rgb(3,jpen)
    if (.not.output%color) then
      ! User requests a Grey output. Convert the color intensities to grey,
      ! see comments in 'rgb_to_grey'
      r = nint( rgb_to_grey(real(r,kind=4),real(g,kind=4),real(b,kind=4)) )
      g = r
      b = r
    endif
    !
  elseif (jpen.le.-8 .and. jpen.ge.-23) then
    ! A user custom color
    jpen = abs(jpen)-7  ! Rescale user color from 1 to 16
    if (output%color) then
      ! Color hardcopy
      r = int(userpens%r(jpen)*255.)
      g = int(userpens%g(jpen)*255.)
      b = int(userpens%b(jpen)*255.)
    elseif (output%background.eq.0) then
      ! Grey hardcopy, black background
      ! First user color = white, last user color = black
      r = nint( (jpen-1)*255./(pen_size-1) )
      g = r
      b = r
    else
      ! Grey hardcopy, white background
      ! First user color = black, last user color = white
      r = nint( (pen_size-jpen)*255./(pen_size-1) )
      g = r
      b = r
    endif
  else
    call gtv_message(seve%e,rname,'Incorrect colour value')
    return
  endif
  !
end subroutine gtv_pencol_id2rgb
!
subroutine gtv_pencol_arg2id(rname,line,iopt,iarg,icol,error)
  use gbl_message
  use gtv_dependencies_interfaces
  use gtv_interfaces, except_this=>gtv_pencol_arg2id
  use gtv_colors
  !---------------------------------------------------------------------
  ! @ public
  ! Decode the a color argument and return the GTV internal identifier
  ! for this color. Input can be:
  !  - A color name, e.g. RED, BLUE, FOREST_GREEN,...
  !  - A Greg color number (1=RED, ..., 23=user custom color)
  !  - An integer variable providing a Greg color number
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  character(len=*), intent(in)    :: line
  integer(kind=4),  intent(in)    :: iopt
  integer(kind=4),  intent(in)    :: iarg
  integer(kind=4),  intent(out)   :: icol
  logical,          intent(inout) :: error
  ! Local
  integer(kind=4) :: nc,ic,icolor
  character(len=32) :: argum
  !
  call sic_ke(line,iopt,iarg,argum,nc,.true.,error)
  if (error)  return
  !
  ! A question mark
  if (argum.eq.'?') then
    call sic_ambigs_list(rname,seve%i,'Choices are:',pen_colors)
    error = .true.  ! So that the command does not go further
    return
  endif
  !
  ! Numbers (PEN /COL 0), formula (PEN /COL j*3),...
  do ic=1,nc
    if (lge(argum(ic:ic),'A') .and. lle(argum(ic:ic),'Z'))  cycle  ! A letter
    if (argum(ic:ic).eq.'_')                                cycle  ! Underscore
    !
    ! Found a character which is not whic we can find in the
    ! pen color names. Must be something with numerics and/or
    ! formula:
    call sic_i4(line,iopt,iarg,icolor,.true.,error)
    if (error)  return
    call gtv_pencol_num2id(rname,icolor,icol,error)
    if (error)  return
    return
  enddo
  !
  ! A variable name
  if (sic_varexist(argum)) then
    call sic_i4(line,iopt,iarg,icolor,.true.,error)
    if (error)  return
    call gtv_pencol_num2id(rname,icolor,icol,error)
    if (error)  return
    return
  endif
  !
  ! A color name
  call gtv_pencol_name2id(rname,argum,icol,error)
  if (error)  return
  !
end subroutine gtv_pencol_arg2id
!
subroutine gtv_pencol_name2id(rname,colname,icol,error)
  use gtv_dependencies_interfaces
  use gtv_interfaces, except_this=>gtv_pencol_name2id
  use gtv_colors
  !---------------------------------------------------------------------
  ! @ public
  ! Return the color identifier given its name. Input must be
  ! uppercased. A full name is not ambiguous. Error if not found.
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  character(len=*), intent(in)    :: colname
  integer(kind=4),  intent(out)   :: icol
  logical,          intent(inout) :: error
  ! Local
  integer(kind=4) :: jcol
  character(len=colname_length) :: key
  !
  ! Try first exact match
  do jcol=1,pen_ncolors
    if (colname.eq.pen_colors(jcol)) then
      icol = jcol
      return
    endif
  enddo
  !
  ! Try with sic_ambigs
  icol = pen_color_foreground  ! Just for safety
  call sic_ambigs(rname,colname,key,icol,pen_colors,pen_ncolors,error)
  if (error)  return
  !
end subroutine gtv_pencol_name2id
!
subroutine gtv_pencol_num2id(rname,colnum,colid,error)
  use gbl_message
  use gtv_dependencies_interfaces
  use gtv_interfaces, except_this=>gtv_pencol_num2id
  !---------------------------------------------------------------------
  ! @ public
  !  Translate Greg color number [-1:23] to GTV color identifier
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  integer(kind=4),  intent(in)    :: colnum
  integer(kind=4),  intent(out)   :: colid
  logical,          intent(inout) :: error
  ! Local
  integer(kind=4), parameter :: mincolor=-1
  integer(kind=4), parameter :: maxcolor=7
  integer(kind=4), parameter :: minuser=8
  integer(kind=4), parameter :: maxuser=23
  ! Order matters here:
  character(len=10), parameter :: colours(mincolor:maxcolor) =  &
    (/ 'NEGATIVE  ','FOREGROUND','RED       ','GREEN     ','BLUE      ',  &
       'CYAN      ','YELLOW    ','MAGENTA   ','BACKGROUND' /)
  !
  if (colnum.ge.mincolor .and. colnum.le.maxcolor) then
    ! A standard color
    call gtv_pencol_name2id(rname,colours(colnum),colid,error)
    if (error)  return
    !
  elseif (colnum.ge.minuser .and. colnum.le.maxuser) then
    ! User color: store value <0 so that GTV knows it has to use
    ! a user color
    colid = -colnum
    !
  else
    call gtv_message(seve%e,rname,'Color number out of range')
    error = .true.
    return
  endif
  !
end subroutine gtv_pencol_num2id
!
function gtv_pencol_id2name(icol)
  use gtv_colors
  !---------------------------------------------------------------------
  ! @ public
  ! Return the color name given its identifier. No error, returns ???
  ! if not found.
  !---------------------------------------------------------------------
  character(len=colname_length) :: gtv_pencol_id2name  ! Function value on return
  integer(kind=4), intent(in) :: icol
  !
  if (icol.ge.1 .and. icol.le.pen_ncolors) then
    ! Standard colors
    gtv_pencol_id2name = pen_colors(icol)
    !
  elseif (icol.le.-8 .and. icol.ge.-23) then
    ! User color
    write(gtv_pencol_id2name,'(A,I0)')  'User ',abs(icol)
  else
    ! Unknown
    gtv_pencol_id2name = '???'
  endif
  !
end function gtv_pencol_id2name
!
subroutine gtv_pendash_arg2id(rname,line,iopt,iarg,idash,error)
  use gbl_message
  use gtv_dependencies_interfaces
  use gtv_interfaces, except_this=>gtv_pendash_arg2id
  use gtv_segatt
  !---------------------------------------------------------------------
  ! @ public
  ! Decode the dash argument and return the corresponding code
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  character(len=*), intent(in)    :: line
  integer(kind=4),  intent(in)    :: iopt
  integer(kind=4),  intent(in)    :: iarg
  integer(kind=4),  intent(out)   :: idash
  logical,          intent(inout) :: error
  ! Local
  character(len=message_length) :: mess
  !
  call sic_i4(line,iopt,iarg,idash,.true.,error)
  if (error) return
  !
  if (idash.lt.min_dash .or. idash.gt.max_dash) then
    write(mess,'(3(a,i0))')  &
      'Dash argument ',idash,' out of range ',min_dash,':',max_dash
    call gtv_message(seve%e,rname,mess)
    error = .true.
    return
  endif
end subroutine gtv_pendash_arg2id
!
subroutine gtv_penwei_arg2val(rname,line,iopt,iarg,wei,error)
  use gbl_message
  use gtv_dependencies_interfaces
  use gtv_interfaces, except_this=>gtv_penwei_arg2val
  !---------------------------------------------------------------------
  ! @ public
  ! Decode the weight argument and return the width (cm) for this
  ! weight. Input can be:
  !  - A Greg weight number (1 to 5 according to 'graisse()' predefined
  !    weights) and possible formulas or variable
  !  - A value of the form "1.23mm" (mm are used for user interface)
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  character(len=*), intent(in)    :: line
  integer(kind=4),  intent(in)    :: iopt
  integer(kind=4),  intent(in)    :: iarg
  real(kind=4),     intent(out)   :: wei
  logical,          intent(inout) :: error
  ! Local
  integer(kind=4) :: nc,iwei
  character(len=32) :: argum
  !
  call sic_ch(line,iopt,iarg,argum,nc,.true.,error)
  if (error)  return
  !
  if (nc.gt.2 .and. argum(nc-1:nc).eq.'mm') then
    call sic_math_real(argum(1:nc-2),nc-2,wei,error)
    if (error)  return
    if (wei.le.0.) then
      call gtv_message(seve%e,rname,'Weight value must be positive')
      error = .true.
      return
    endif
    wei = wei/10.0  ! mm to cm
  else
    call sic_i4(line,iopt,iarg,iwei,.true.,error)
    if (error)  return
    call gtv_penwei_num2val(rname,iwei,wei,error)
    if (error)  return
  endif
  !
end subroutine gtv_penwei_arg2val
!
subroutine gtv_penwei_num2val(rname,iwei,wei,error)
  use gbl_message
  use gtv_interfaces, except_this=>gtv_penwei_num2val
  use gtv_segatt
  !---------------------------------------------------------------------
  ! @ public
  ! Decode the Greg weight number as the actual width (cm)
  !---------------------------------------------------------------------
  character(len=*), intent(in)    :: rname
  integer(kind=4),  intent(in)    :: iwei
  real(kind=4),     intent(out)   :: wei
  logical,          intent(inout) :: error
  ! Local
  character(len=message_length) :: mess
  !
  if (iwei.lt.min_weight .or. iwei.gt.max_weight) then
    write(mess,'(A,I0,A,2I2)')  &
      'Weight code ',iwei,' out of range',min_weight,max_weight
    call gtv_message(seve%e,rname,mess)
    error = .true.
    return
  endif
  wei = graisse(iwei)
  !
end subroutine gtv_penwei_num2val
!
function gtv_penwei_tostr(wei)
  use gtv_segatt
  !---------------------------------------------------------------------
  ! @ public
  !  Convert the input weight value (cm) to a user friendly output (mm),
  ! e.g.
  !   "1 0.15mm" for predefined weights, or
  !   "  1.23mm" for custom weights
  !---------------------------------------------------------------------
  character(len=8) :: gtv_penwei_tostr  ! Function value on return
  real(kind=4), intent(in) :: wei  ! [cm]
  ! Local
  integer(kind=4) :: iw
  !
  if (wei.le.0.0) then
    gtv_penwei_tostr = '???'
    return
  endif
  !
  do iw=min_weight,max_weight
    if (wei.eq.graisse(iw)) then
      write(gtv_penwei_tostr,'(I1,1X,F4.2,A2)')  iw,wei*10.0,'mm'
      return
    endif
  enddo
  !
  ! Not a predefined weight
  write(gtv_penwei_tostr,'(2X,F4.2,A2)')  wei*10.0,'mm'
  !
end function gtv_penwei_tostr
