3D Text and Fonts

So I tried to get a little creative this time around and use something other than Arial as my default font in my Title Block tool (which uses the add_3d_text method) that is part of my Medeek Project extension:

It worked great on my system (I have Microsoft Word installed), and of course failed miserably on anyone who did not have this font installed.

The slightly annoying thing is that SketchUp’s error doesn’t really tell you that the font is the issue, it just fails to create the 3D text.

Rather than hard code in a select few fonts that I know everyone should have I think it would be better to have my plugin code determine what is available on the users system and then provide those as options. Obviously not all fonts work with this method, so it may be more complicated than just enumerating all the installed fonts on a Mac or PC.

How would I go about doing that? Does anyone have any experience with doing this sort of thing?

Up until now I’ve purposely limited my plugins to a small set of fonts that I assumed were standard across any platform:

Arial
Tahoma
Helvetica
Times New Roman
Courier New
Verdana
Georgia
Palatino
Trebuchet MS

You might allow the user to add or to or subtract from the list ?


On Windows the filenames are:

fonts = Dir.glob("C:/Windows/Fonts/*.ttf")

You’ll notice often the italics have an “i” in the name, bold a “b”, light a “l”, etc. with combinations.

1 Like

Now I need to figure out what to do for Mac users.

Also the installed font names are enumerated in the Windows registry.
You can read the entries using the Fiddle I think

The key is here:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts

1 Like

I use this example…

Note how it uses some constants like FOLDER and NAME which you’ll need to adjust for your set up…
This example is looking for a font name “Txt”, with a file-name of “txt_____.ttf”, shipped with this particular custom extension, and which the user needs to install first off…
It also defaults to “Arial” if the specified font is not found, so you probably want to adjust these too…
If works for PC & MAC, a MAC will crash if the font is missing, a PC will simple do nothing at all !

### Font check
@@font = "Txt"
@@fontname = "txt_____.ttf"
@@txtfont = File.join(FOLDER, @@fontname)
if RUBY_PLATFORM.downcase =~ /mswin|mingw/ ### pc
	find = false
	Dir.entries("C:/Windows/Fonts").grep(/ttf$/).grep(/^txt/).each{|e|
		if e =~ /^txt_/
			find = true
			break
		end
	}	
	if find
		#UI.messagebox("#{NAME}:\n\n'#{@@font}' font installed.")
	else ### missing
		UI.messagebox("#{NAME}:\n\n'#{@@font}' font is NOT installed.\nWithout it References will be in 'Arial' font.\nInstall it and restart.\nIf you do not have permission, get an 'Admin' to install it from:\n#{@@txtfont}")
		@@font = "Arial"
	end
else ### mac
	if find = %x(mdfind -onlyin ~/Library/Fonts 'kMDItemKind  == "TrueType font" && kMDItemDisplayName  == "#{@@fontname}"')
	elsif find = %x(mdfind -onlyin /Library/Fonts 'kMDItemKind  == "TrueType font" && kMDItemDisplayName  == "#{@@fontname}"')
	elsif find = %x(mdfind -onlyin /Network/Library/Fonts 'kMDItemKind  == "TrueType font" && kMDItemDisplayName  == "#{@@fontname}"')
	elsif find = %x(mdfind -onlyin /System/Library/Fonts 'kMDItemKind  == "TrueType font" && kMDItemDisplayName  == "#{@@fontname}"')
	elsif find = %x(mdfind -onlyin /System Folder/Library/Fonts 'kMDItemKind  == "TrueType font" && kMDItemDisplayName  == "#{@@fontname}"')
	else
		find=false
	end
	if find && !find.empty?
		#UI.messagebox("#{@@font} font exists #{find}.")
	else
		UI.messagebox("#{NAME}:\n\n'#{@@font}' font is NOT installed.\nWithout it References will be in 'Arial' font.\nInstall it and restart.\nIf you do not have permission, get an 'Admin' to install it from:\n#{@@txtfont}")
		@@font = "Arial"
	end
end
2 Likes

Actually an easier way would be to use a backtick (aka %x) string to use Windows’ reg.exe command line utility.

def get_fonts # MS Windows
  fonts = []
  Dir.chdir(__dir__) do |path|
    %x[reg export "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" "fonts.txt" /y]
    sleep 0.5
    lines = File.readlines(
      'fonts.txt', chomp: true, encoding: 'UTF-16LE:UTF-8'
    )
    lines.each { |line|
      next unless line.start_with?('"')
      font = line.split('"=').first
      font.delete_prefix!('"')
      font.delete_suffix!(' (TrueType)')
      fonts << font
    }
    puts "Fonts names retrieved: #{fonts.size}"
    nil
  end
  return fonts
end

The reg.exe utility exports in .reg format which is importable back in any windows registry. So I force a .txt extension. Also it writes in UTF-16 Little Endian w/ Bit Order Mark encoding, so we must make sure to use this external encoding for IO methods.

1 Like

Here is an even simpler approach that does not use an external program,
… nor need to write an external font list file:

module SomeAuthor
  module Fonts

    require 'win32/registry'

    extend self

    MACHINE ||= Win32::Registry::HKEY_LOCAL_MACHINE
    FONTKEY ||= 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts'
    
    def get_fonts
      MACHINE.open(FONTKEY) do |fontkey|
        fontlist = []
        fontkey.each_value { |name, type, data|
          name.delete_suffix!(' (TrueType)')
          fontlist << name 
        }
        fontlist
      end
    end ###

  end # submodule Fonts
end # toplevel namespace
1 Like

Okay, I’m am going to try this one and see if I can make it work.

1 Like

I wrote it as a library module, but could wrap it all up into 1 method …

    def get_fonts
      require 'win32/registry' if !defined?(::Win32) || !defined?(::Win32::Registry)
      Win32::Registry::HKEY_LOCAL_MACHINE.open(
        'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts'
      ) do |fontkey|
        fontlist = []
        fontkey.each_value { |name, type, data|
          name.delete_suffix!(' (TrueType)')
          fontlist << name 
        }
        fontlist
      end
    end ### get_fonts

1 Like