# frozen_string_literal: true #========================== # rsyntaxtree.rb #========================== # # Facade of rsyntaxtree library. When loaded by a driver script, it does all # the necessary 'require' to use the library. # Copyright (c) 2007-2023 Yoichiro Hasebe <yohasebe@gmail.com> FONT_DIR = File.expand_path(File.join(__dir__, "/../fonts")) ETYPE_NODE = 1 ETYPE_LEAF = 2 SUBSCRIPT_CONST = 0.7 FONT_SCALING = 2 WHITESPACE_BLOCK = "■" class RSTError < StandardError def initialize(msg = "Error: something unexpected occurred") msg.gsub!(WHITESPACE_BLOCK, "<>") super msg end end require_relative 'rsyntaxtree/utils' require_relative 'rsyntaxtree/element' require_relative 'rsyntaxtree/elementlist' require_relative 'rsyntaxtree/svg_graph' require_relative 'rsyntaxtree/version' require_relative 'rsyntaxtree/string_parser' require 'cgi' require 'rsvg2' require 'rmagick' module RSyntaxTree class RSGenerator def initialize(params = {}) new_params = {} fontset = {} params.each do |keystr, value| key = keystr.to_sym case key when :data data = value data = data.gsub('-AMP-', '&') .gsub('-PERCENT-', "%") .gsub('-PRIME-', "'") .gsub('-SCOLON-', ';') .gsub('-OABRACKET-', '<') .gsub('-CABRACKET-', '>') .gsub('¥¥', '\¥') .gsub(/(?<!\\)¥/, "\\") new_params[key] = data when :symmetrize, :color, :transparent, :polyline new_params[key] = value && (value != "off" && value != "false") ? true : false when :fontsize new_params[key] = value.to_i * FONT_SCALING when :margin new_params[key] = value.to_i * FONT_SCALING * 5 when :vheight new_params[key] = value.to_f when :fontstyle case value when "noto-sans", "sans" fontset[:normal] = FONT_DIR + "/NotoSans-Regular.ttf" fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf" fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf" fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf" fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf" fontset[:cjk] = FONT_DIR + "/NotoSansJP-Regular.otf" fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf" new_params[:fontstyle] = "sans" when "noto-serif", "serif" fontset[:normal] = FONT_DIR + "/NotoSerif-Regular.ttf" fontset[:italic] = FONT_DIR + "/NotoSerif-Italic.ttf" fontset[:bold] = FONT_DIR + "/NotoSerif-Bold.ttf" fontset[:bolditalic] = FONT_DIR + "/NotoSerif-BoldItalic.ttf" fontset[:math] = FONT_DIR + "/latinmodern-math.otf" fontset[:cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf" fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf" new_params[:fontstyle] = "serif" when "cjk zenhei", "cjk" fontset[:normal] = FONT_DIR + "/wqy-zenhei.ttf" fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf" fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf" fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf" fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf" fontset[:cjk] = FONT_DIR + "/wqy-zenhei.ttf" fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf" new_params[:fontstyle] = "cjk" end else new_params[key] = value end end # defaults to the following @params = { symmetrize: true, color: true, transparent: false, fontsize: 16, format: "png", leafstyle: "auto", filename: "syntree", data: "", margin: 0, vheight: 1.0, polyline: false } @params.merge! new_params @params[:fontsize] = @params[:fontsize] * FONT_SCALING @params[:margin] = @params[:margin] * FONT_SCALING @params[:fontset] = fontset single_x_metrics = FontMetrics.get_metrics("X", fontset[:normal], @params[:fontsize], :normal, :normal) @global = {} @global[:single_x_metrics] = single_x_metrics @global[:height_connector_to_text] = single_x_metrics.height / 2.0 @global[:single_line_height] = single_x_metrics.height * 2.0 @global[:width_half_x] = single_x_metrics.width / 2.0 @global[:height_connector] = single_x_metrics.height * 1.0 * @params[:vheight] @global[:h_gap_between_nodes] = single_x_metrics.width * 0.8 @global[:box_vertical_margin] = single_x_metrics.height * 0.8 end def self.check_data(text) raise RSTError, "Error: input text is empty" if text.to_s == "" StringParser.valid?(text) end def draw_png(binary = false) svg = draw_svg rsvg = RSVG::Handle.new_from_data(svg) dim = rsvg.dimensions surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, dim.width, dim.height) context = Cairo::Context.new(surface) context.render_rsvg_handle(rsvg) b = StringIO.new surface.write_to_png(b) if binary b else b.string end end def draw_svg sp = StringParser.new(@params[:data].gsub('&', '&'), @params[:fontset], @params[:fontsize], @global) sp.parse graph = SVGGraph.new(sp.get_elementlist, @params, @global) graph.svg_data end def draw_tree svg = draw_svg image, _data = Magick::Image.from_blob(svg) do |im| im.format = 'svg' end if @params[:format] == "png" image = draw_png(false) else image.to_blob do |im| im.format = @params[:format].upcase end end image end end end