From: Tan Kian-ting Date: Sat, 30 Sep 2023 14:45:38 +0000 (+0800) Subject: add tyoesetting languages. X-Git-Url: https://git.kianting.info/?a=commitdiff_plain;h=c4b1175d9c499dfbcdba3a7a437e144bdbabbb7c;p=archivesOfToyLang add tyoesetting languages. --- diff --git a/ataabu-with-git.tar.gz b/ataabu-with-git.tar.gz new file mode 100644 index 0000000..b9d2934 Binary files /dev/null and b/ataabu-with-git.tar.gz differ diff --git a/tshunhue-with-git.tar.gz b/tshunhue-with-git.tar.gz new file mode 100644 index 0000000..15cdd33 Binary files /dev/null and b/tshunhue-with-git.tar.gz differ diff --git a/typesettings/tsulip-with-git.tar.gz b/typesettings/tsulip-with-git.tar.gz new file mode 100644 index 0000000..f22ea5e Binary files /dev/null and b/typesettings/tsulip-with-git.tar.gz differ diff --git a/typesettings/tsulip/LICENSE b/typesettings/tsulip/LICENSE new file mode 100644 index 0000000..4125c24 --- /dev/null +++ b/typesettings/tsulip/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Tan, kian-ting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/typesettings/tsulip/README.md b/typesettings/tsulip/README.md new file mode 100644 index 0000000..66eb878 --- /dev/null +++ b/typesettings/tsulip/README.md @@ -0,0 +1,2 @@ +# tsulip +a little typesetter. diff --git a/typesettings/tsulip/TODO b/typesettings/tsulip/TODO new file mode 100644 index 0000000..7bfef18 --- /dev/null +++ b/typesettings/tsulip/TODO @@ -0,0 +1,2 @@ +1. Token->Parser +2. main.rs 下面的「如何顯示文字於pdf的問題」 \ No newline at end of file diff --git a/typesettings/tsulip/src/main.rs b/typesettings/tsulip/src/main.rs new file mode 100644 index 0000000..d13bc79 --- /dev/null +++ b/typesettings/tsulip/src/main.rs @@ -0,0 +1,213 @@ +extern crate pango; +extern crate cairo; +use std::env; +use regex::Regex; +use std::collections::HashMap; +use std::fs; +use clap::{Arg, App}; + +mod pdf_interface; + +/* +ls = ["(", "3", ")", "1", ")"] + + +def consume(ls): + return ls[1:] + +def parse(): + global ls + if ls[0] == "(": + ls = consume(ls) + a = [] + print(ls) + while ls[0] != ")": + b = parse() + a.append(b) + ls = consume(ls) + return a + else: + + a = ls[0] + ls = consume(ls) + return a +g = parse() +if ls != []: + print("Unexpected right parenthesis ).") +*/ + +/* +mod author; +use author::* */ + +/// DataToken store the tokenized data. +/// - line_no : num of line +/// - col_no : num of col +/// - text : text as string +#[derive(Debug)] +struct DataToken{ + text: String, + line_no: i32, + col_no: i32, +} + +/// DatatokenAST - Storing Data token AST. +/// Node -> for a node +/// Tree -> for a subtree +enum DataTokenAST { + Node(DataToken), + Tree(Vec), + +} + +fn tokenizer(data: String) -> Vec{ + // pattern list. int, float, parenthesis left and right, spaces, identifier, and string + let pattern_list = vec!["\\d+", "\\d+[.]\\d+", "[(]", "[)]", "[ \t\n]", "[^() \t]+", "\"([ \t]|[^\\\\]|\\\\)*\""]; + let joined_string = pattern_list.join("|"); // join the string. + let regex_string = format!(r"({})", joined_string); + let re_pattern = Regex::new(®ex_string).unwrap(); + + let mut token_list : Vec = vec![]; + + let mut line_no = 1; + let mut col_no: i32 = 1; + for cap in re_pattern.captures_iter(&data) { + let text = &cap[0]; + let mut text_length: i32 = (text.chars().count()) as i32; + println!("Month: {}", &cap[0]); + let data_token = DataToken{text : (text.to_string()), line_no: line_no, col_no:col_no}; + token_list.push(data_token); + + if text == "\n"{ + col_no = 1; + line_no += 1; + }else{ + col_no += text_length; + } + + } + + return token_list; +} + +/// parsing the token to let it be a AST. +/// - data_token_list : the list of tokens. +/// return DataTokenAST. +fn token_parser(data_token_list : Vec){ + // remove the spaces + let data_token_list_remove_space = data_token_list + .iter().filter(| x|{ + if (vec!["\n", "\t", " "].contains(&(x.text.as_str()))) + {return false;} + {return true;}}); + println!("{:?}", data_token_list_remove_space); +} + +/// +/// main function. +fn main() { + + // version number of the typesetter. + let version_num = "0.0.1"; + + // input command line + let args_matches = App::new("tsulip") + .version(version_num) + .author("Tan, Kian-ting ") + .about("A litter typesetter.") + .arg(Arg::with_name("input") + .short("i") + .long("input") + .takes_value(true) + .help("input filepath")) + .arg(Arg::with_name("output") + .short("o") + .long("output") + .takes_value(true) + .help("destination pdf output.")) + .get_matches(); + + + + + let mut input_file_path_raw = args_matches.values_of_lossy("input"); + + let mut input_file_path = "".to_string(); + + match input_file_path_raw { + Some(x) => input_file_path = x[0].clone(), + None => {println!("missing input path."); std::process::exit(1) ;} + } + + let input_file_path_pattern = Regex::new(r"^(.+)[.]tsu$").unwrap(); + + // if file name unmatched, shoew the error: + if !(input_file_path_pattern.is_match(&input_file_path)){ + println!("the file_name {} doesn't match the pattern. It should be \".tsu\".", input_file_path); + std::process::exit(1) ; + } + + let data = fs::read_to_string(input_file_path.clone()).expect((format!("Error: unable to read the file in {}", input_file_path)).as_str()); + + let tokenized = tokenizer(data); + + + + + + + + + // destination of the pdf. + let default_output_pdf_path = format!("{}{}", &input_file_path[0..input_file_path.chars().count()-3], "pdf"); + + let mut output_pdf_path_raw = args_matches.values_of_lossy("output") + .unwrap_or(vec![default_output_pdf_path.to_string()]); + let output_pdf_path = &output_pdf_path_raw[0]; + + let pdf_size = "a4"; + + let mut size_table = HashMap::new(); + + // + // pepersize -> px (w, h) comparison table + size_table.insert("a4", [595.0, 842.0]); + + // setting pdf_size. + let mut pdf_width = 595.0; + let mut pdf_height = 842.0; + if !size_table.contains_key(pdf_size){ + println!("the size {} is unfound. using a4 size.", pdf_size);} + else{ + pdf_width = size_table[pdf_size][0]; + pdf_height = size_table[pdf_size][1]; + } + + println!("{}", pdf_width); + + // create page. + let surface = cairo::PdfSurface::new(110.0, 110.0, "/tmp/a.pdf").expect("Couldn't create surface!"); + let ctx = cairo::Context::new(&surface).expect("running error"); + ctx.scale(110.0, 110.0); // Normalizing the canvas + + ctx.set_source_rgba(0.8, 1.0, 1.0, 0.5); // 設定顏色 + ctx.paint().ok();// 設定背景顏色 + ctx.save(); + + /* + let string = "愛".to_string(); + let x = 100.0; + let y = 100.0; + let font_family = "Noto Sans CJK TC".to_string(); + let font_style = "".to_string(); + let font_weight = "".to_string(); + let color = "#ffffff".to_string(); + */ + // pdf_interface::put_chars(string, x, y , font_family, font_style, font_weight, color, &cxt); + + + + std::process::exit(0) ; +} + + diff --git a/typesettings/tsulip/src/pdf_interface.rs b/typesettings/tsulip/src/pdf_interface.rs new file mode 100644 index 0000000..df441d0 --- /dev/null +++ b/typesettings/tsulip/src/pdf_interface.rs @@ -0,0 +1,75 @@ +extern crate pango; +extern crate cairo; +use pangocairo; + +/// convert font_style string to Style class +/// - font_style : Nommal, Oblique, Italic, or omitted as string. +fn convert_string_to_font_style(font_style_str : String)->pango::Style{ + if font_style_str == "" || font_style_str == "Normal"{ + return pango::Style::Normal; + }else if font_style_str == "Oblique"{ + return pango::Style::Oblique; + }else if font_style_str == "Italic"{ + return pango::Style::Italic; + }else { + println!("the font-style is illegal: {}, set to Normal", font_style_str); + return pango::Style::Normal; + } + +} + +/// convert font_weight string to Weight class +/// - font_style : Thin, Ultralight, Light, Semilight, Book, +/// Normal, Medium, Semibold, Bold, Ultrabold, Heavy, +/// Ultraheavy, or omitted as string. +fn convert_string_to_font_weight(font_weight_str : String)->pango::Weight{ + match font_weight_str.as_str(){ + "" => pango::Weight::Normal, + "Normal" => pango::Weight::Normal, + "Thin" => pango::Weight::Thin, + "Ultralight" => pango::Weight::Ultralight, + "Light" => pango::Weight::Light, + "Semilight" => pango::Weight::Semilight, + "Book" => pango::Weight::Book, + "Medium" => pango::Weight::Medium, + "Semibold" => pango::Weight::Semibold, + "Bold" => pango::Weight::Bold, + "Ultrabold" => pango::Weight::Ultrabold, + "Heavy" => pango::Weight::Heavy, + "Ultraheavy" => pango::Weight::Ultraheavy, + _ => {println!("the font-weight is illegal: {}, set to Normal", font_weight_str); pango::Weight::Normal} + + } + +} + + +/// put chars to the pdf +/// - x = position from left in px +/// - y = position from bottom in px +pub(crate) fn put_chars(text : String, x : f64, y : f64 , font_family : String, + font_style_str : String, font_weight_str : String, color : String, context: &cairo::Context){ + let mut font = pango::FontDescription::new(); + font.set_family(font_family.as_str()); + + let font_style = convert_string_to_font_style(font_style_str); + font.set_style(font_style); + + let weight = convert_string_to_font_weight(font_weight_str); + font.set_weight(weight); + + println!("{:?}", font); + + let fontmap = pangocairo::FontMap::default().unwrap(); + let pango_context = pango::Context::new(); + let pango_layout = pango::Layout::new(&pango_context); + pango_context.set_font_map(&fontmap); + pango_layout.set_font_description(Some(&font)); + pango_layout.set_text(text.as_str()); + + + context.move_to(x, y); + pangocairo::show_layout(&context, &pango_layout); + // context.save(); + +} \ No newline at end of file diff --git a/typesettings/tsulip/test.pdf b/typesettings/tsulip/test.pdf new file mode 100644 index 0000000..9a675e2 Binary files /dev/null and b/typesettings/tsulip/test.pdf differ diff --git a/typesettings/tsulip/test.tsu b/typesettings/tsulip/test.tsu new file mode 100644 index 0000000..e55e57d --- /dev/null +++ b/typesettings/tsulip/test.tsu @@ -0,0 +1,22 @@ +(% 建立網格 ) + +(create-page "a4") + + +(create-frame 1 "main1" 100 100 400 500 "main2") + +(define page-num 0) +(define add-page (lambda () (begin (new-page)(set! page-num (+ page-num 1)) (create-frame page-num 100 100 400 500 (str-append "main" (+ page-num 1)))) + +(define font_color "#ffffff") +(define font_size 10) +(define font_style "Italic") +(define font_family "Noto Sans CJK TC") +(define font_weight "Bold") +(define old_var "") + +(define frame_1_text (string-append "願您們平安。" (void_to_empty_str (set! font_size 12)) "Peace upon you.")) + +(text_to_box) + +(input_boxes (1 "1st") frame_1_text) diff --git a/typesettings/uahli-with-git.tar.gz b/typesettings/uahli-with-git.tar.gz new file mode 100644 index 0000000..76b485c Binary files /dev/null and b/typesettings/uahli-with-git.tar.gz differ diff --git a/typesettings/uahli/.vscode/launch.json b/typesettings/uahli/.vscode/launch.json new file mode 100644 index 0000000..c101096 --- /dev/null +++ b/typesettings/uahli/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // 使用 IntelliSense 以得知可用的屬性。 + // 暫留以檢視現有屬性的描述。 + // 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "cppdbg", + "request": "launch", + "name": "(Linux) Launch", + "program": "${workspaceRoot}/target/debug/${workspaceFolderBasename}", + "args": [], + "cwd": "${workspaceRoot}", + "externalConsole": false, + "MIMode": "gdb", + "preLaunchTask": "Build", + } + ] +} \ No newline at end of file diff --git a/typesettings/uahli/.vscode/tasks.json b/typesettings/uahli/.vscode/tasks.json new file mode 100644 index 0000000..9ba9857 --- /dev/null +++ b/typesettings/uahli/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "command": "cargo", + "args": ["build"], + } + ] +} \ No newline at end of file diff --git a/typesettings/uahli/Cargo.toml b/typesettings/uahli/Cargo.toml new file mode 100644 index 0000000..a61e7d6 --- /dev/null +++ b/typesettings/uahli/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "uahli" +version = "0.1.0" +authors = ["Yoxem Chen "] +edition = "2018" + +[dependencies] +harfbuzz_rs = "2.0.1" +cairo-rs = { version = "0.14.1", features = ["pdf","ps"]} +pango = { version ="0.14.3" ,features = ["v1_42"]} +pangocairo = "0.14.0" +fontconfig = "0.2.1" +regex = "1.5.4" diff --git a/typesettings/uahli/LICENSE b/typesettings/uahli/LICENSE new file mode 100644 index 0000000..769ddca --- /dev/null +++ b/typesettings/uahli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Chen, Chien-ting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/typesettings/uahli/README.md b/typesettings/uahli/README.md new file mode 100644 index 0000000..f07c933 --- /dev/null +++ b/typesettings/uahli/README.md @@ -0,0 +1,2 @@ +# uahli +typesetting toy diff --git a/typesettings/uahli/src/main.rs b/typesettings/uahli/src/main.rs new file mode 100644 index 0000000..4563871 --- /dev/null +++ b/typesettings/uahli/src/main.rs @@ -0,0 +1,543 @@ +extern crate cairo; +extern crate fontconfig; +extern crate pango; +use std::convert::TryInto; +use std::collections::HashMap; +use std::str; +use harfbuzz_rs::*; +use pangocairo; +use pangocairo::prelude::FontMapExt; +use regex::Regex; +use std::str::FromStr; + + + +/// +/// +/// storing line data. +/// - content : the vec of the line content of Option +/// - div_text_offset: the offset between div width and text width +#[derive(Debug)] +struct Line<'a>{ + content: Vec<&'a BoxCoodInfo>, + div_text_offset: f64 +} + +pub struct MinRaggedLayouter { + hashmap_cost: HashMap, + hashmap_route: HashMap, + words: String, + path: Vec + } + + /* +impl MinRaggedLayouter { + /// + /// counting total_cost of a line ragged cost. + /// - words : the words listed here. + /// - dest : destination (k) + /// - maxwidth: in pt. + pub fn total(&mut self, words : Vec>, dest : u32, maxwidth : f64 ) -> f64 { + + + } + }*/ + +/// +/// +/// Get the infomation of a coodinrate of a box (text slice). +/// - text: the text of it. +/// - width: the x length of moving in pt. +/// - height: the y length of moving in pt +/// - x_offset: the x moving from the baseline in pt +/// - y_offset: the y moving from the baseline in pt +/// +#[derive(Debug)] +struct BoxCoodInfo { + text: String, + width: f64, + height: f64, + x_offset: f64, + y_offset: f64 +} +/// +/// RgbColor: storing 0~255 rgb color code +/// - red: 0 ~ 255 +/// - green: 0 ~ 255 +/// - blue: 0 ~ 255 +#[derive(Debug)] +struct RgbColor{ + red: u32, + green: u32, + blue: u32 +} +/// +/// The structure storing a font and its attribution. +/// - name : font name . eg. "FreeSans" +/// - style : font style. eg. "Italic" +/// - size : in pt. eg. "32" for 32pt +/// - variations. variation of a opentype font. eg. `vec![Variation::new(b"wght", 800.0);]` +/// - features. features of a opentype font. eg. `vec![Feature::new(b"calt", 1, 0..)]` +/// +#[derive(Debug)] +struct FontStruct<'a> { + name: &'a str, + style: &'a str, + size: u32, + variations : &'a [Variation], + features: &'a [Feature] +} + +/// a Div text layout block. unit: pt +/// - x: x-axis in pt (from left) +/// - y: y-axis in pt (from top) +/// - width: Div width in pt +/// - height: Div height in pt +/// - lineskip: the skip between the baseline of 2 lines in pt +/// - direction: Rtl, Ltr, Btt, Ttb +/// - color: #ffffff -like hex html color code +#[derive(Debug)] +struct Div<'a>{ + x: f64, + y: f64, + width:f64, + height: f64, + lineskip: f64, + language: &'a str, + direction: harfbuzz_rs::Direction, + color: String +} + +/// get the cood infomation of the input box. +/// - text : the render text +/// - font_name: "FreeSans", etc +/// - font_style: "Bold", etc +/// - font_size_pt: 16, etc +/// - direction: Ltr, Rtl, etc, +/// - variations: Opentype variation axis list +/// - features: Opentype feature list +fn get_box_cood_info(text : &str, font_name : &str, font_style : &str, font_size_pt : u32, language: &str, + direction : harfbuzz_rs::Direction, variations : &[Variation], features: & [Feature]) + -> Option { + // let font_combined = format!("{} {}", font_name, font_style); + + let fc = fontconfig::Fontconfig::new()?; + let font = fc.find(font_name, Some(font_style))?; + let path = font.path.to_str()?; + // println!("{}", path);; + + let index = 0; //< face index in the font file + let face = Face::from_file(path, index).ok()?; + + let mut font = Font::new(face); // setting the font + + if !variations.is_empty(){ + font.set_variations(&variations); + } + + font.set_scale((font_size_pt*64).try_into().unwrap(), (font_size_pt*64).try_into().unwrap()); // setting the pt size + + let hb_language = harfbuzz_rs::Language::from_str(language).unwrap(); + + let mut buffer = UnicodeBuffer::new().set_direction(direction).set_language(hb_language).add_str(text); + + + // shape the text box + let output = shape(&font, buffer, &features); + + // The results of the shaping operation are stored in the `output` buffer. + let positions = output.get_glyph_positions(); + let infos = output.get_glyph_infos(); + + + assert_eq!(positions.len(), infos.len()); + + let mut box_cood = BoxCoodInfo{text: text.to_string(), width: 0.0, height : 0.0, x_offset: 0.0, y_offset : 0.0}; + + for position in positions{ + + + let x_advance = (position.x_advance) as f64/64.0; + let y_advance = (position.y_advance) as f64/64.0; + let x_offset = (position.x_offset) as f64/64.0; + let y_offset = (position.y_offset) as f64/64.0; + + + + + // set the max_x(y)_advance as the box_cood.x(y)_advance + if box_cood.x_offset `"wght=800.0,wdth=50.0,"` +/// - vars: variation list +fn font_variant_list_to_string(vars : &[Variation]) -> String{ + let mut string : String; + string = "".to_string(); + for i in vars{ + let var_combined = format!("{}={},", i.tag(), i.value()); + string.push_str(&var_combined); + } + return string; + +} + +/// +/// +/// convert hex color code to rgb 256 number. eg. +/// #ffffff -> RgbColor{red:256, green:256, blue:256} +/// - hex : the hex color code to be input +fn hex_color_code_to_int(hex : &str)->Option{ + let pattern = Regex::new(r"#(?P[0-9a-fA-F]{2})(?P[0-9a-fA-F]{2})(?P[0-9a-fA-F]{2})").unwrap(); + let caps = pattern.captures(hex).unwrap(); + let mut rgb = RgbColor{red:0, green:0, blue:0}; + let r = caps.name("r")?; + rgb.red = u32::from_str_radix(r.as_str(),16).ok()?; + let g = caps.name("g")?; + rgb.green = u32::from_str_radix(g.as_str(),16).ok()?; + let b = caps.name("b")?; + rgb.blue = u32::from_str_radix(b.as_str(),16).ok()?; + + return Some(rgb); + +} + +/// +/// +/// show `text` in `canva` at `x` and `y` with `font_struct` +/// text : the text to be rendered. +/// font_sruct: font and its attributes +/// x : x-axis coord in pt. +/// y : y-axis coord in pt. +/// color : hex color `#000000`, etc +/// lang: string eg. "en", "zh", etc. +/// direction: harfbuzz_rs::Direction +/// canva : cairo canvas +/// return box_cood if it runs successfully. +fn layout_text(text : &str, mut font_struct: &FontStruct, x : f64, y: f64,color: &str, lang: &str, direction: harfbuzz_rs::Direction, mut canva: &cairo::Context) +->Option<()> +{ + let fontmap = pangocairo::FontMap::default().unwrap(); + + // you have to multiply 0.75 or you'll get a bug. + let font_combined = format!("{} {} {}", font_struct.name, font_struct.style, (font_struct.size as f64) * 0.75); + + let mut font_with_style = pango::FontDescription::from_string(&font_combined); + // pango_font_style.set_absolute_size((font_struct.size * 1024).into()); + font_with_style.set_variations(&font_variant_list_to_string(font_struct.variations)); + let pango_cxt = pango::Context::new(); + // println!("{:?}", pango::AttrType::Fallback); + + let _pango_cairo_font = fontmap.load_font(&pango_cxt, &font_with_style); + + //let box_cood = get_box_cood_info(text, font_struct.name, font_struct.style, font_struct.size, direction, font_struct.variations, &[])?; + + pango_cxt.set_language(&pango::language::Language::from_string(lang)); + + pango_cxt.set_font_map(&fontmap); + let pango_layout = pango::Layout::new(&pango_cxt); + pango_layout.set_font_description(Some(&font_with_style)); + pango_layout.set_text(text); + + // setting the color + canva.save().ok(); + let color_rgb = hex_color_code_to_int(color)?; + + canva.set_source_rgb(color_rgb.red as f64/256.0, color_rgb.green as f64/256.0, color_rgb.blue as f64/256.0); + + + canva.move_to(x, y); + pangocairo::show_layout(&canva, &pango_layout); + + canva.restore().ok(); + canva.move_to(0.0, 0.0); + + return Some(()); +} + + + +/// +/// typesetting for greedy algorithm using unragged. +/// for arguments, see `greedy_typesetting` +fn greedy_typesetting(box_coord_vec : Vec>, block: Div, font: FontStruct, cxt : &cairo::Context, ragged : bool){ + + //get the space width. + let mut space_width = 0.0; + + let space_box_cood = get_box_cood_info(" ", font.name, font.style, font.size, block.language, block.direction, font.variations, font.features); + match space_box_cood { + Some(inner) =>{ + space_width = inner.width; + } + None=>println!("The space width can't be defined. Set it to 0.") + } + let mut lines = vec![]; // store lines + let mut line = vec![]; // store a line + + let mut current_x = block.x; + let mut current_y = block.y; + + let mut div_txt_offset = 0.0; // the offset between div width and text width + + let mut is_overflowed = false; + + for i in &box_coord_vec{ + match i { + Some(inner) =>{ + let mut inner_width = inner.width; + if Regex::new(r"[ \t\n]+").unwrap().is_match(&(inner.text)){ + inner_width = space_width; + } + + if (current_x + inner_width) <= block.x + block.width { + line.push(inner); + + // if inner is not space, set the div_txt_offset + if !is_space(&inner.text){ + div_txt_offset = block.x + block.width - (current_x + inner.width); + } + current_x += inner_width; + // try to add a new line + }else{ + + + current_x = block.x; + current_y += block.lineskip; + + let div_txt_offset_clone = div_txt_offset.clone(); + + let line_clone = line.clone(); + let line_content = Line{ + content: line_clone, + div_text_offset: div_txt_offset_clone}; + + lines.push(line_content); + + + // if beneath the margin of the botton, don't layout it and break + if current_y > block.y + block.height{ + is_overflowed = true; + break; + } + else{ + /*println!("{:?}", space_width); + println!("{:?}", div_txt_offset); + println!("{:?}", block.x + block.width);*/ + + line = vec![]; + div_txt_offset = 0.0; + + // if it's non space, add it. + if !Regex::new(r"[ \t\n]+").unwrap().is_match(&(inner.text)){ + line.push(inner); + current_x += inner_width; + } + + } + + } + } + None => println!("The text segment can't be layouted."), + } + + + } + + // if it's not overflowed, push the last line. + + if !is_overflowed{ + let div_txt_offset_clone = div_txt_offset.clone(); + + let line_clone = line.clone(); + let line_content = Line{ + content: line_clone, + div_text_offset: div_txt_offset_clone}; + + lines.push(line_content); + + }else{ + + } + + // layout the characters + if ragged == true{ + current_y = block.y; + current_x = block.x; + for i in 0..lines.len(){ + for j in 0..lines[i].content.len(){ + let con = lines[i].content[j]; + + let mut content_width = con.width; + // if it's space, set it to space_width. + if is_space(&(con.text)){ + content_width = space_width; + } + + + layout_text(&(con.text), &font, current_x, current_y, &(block.color) ,block.language, block.direction , &cxt); + current_x += content_width; + } + current_y += block.lineskip; + current_x = block.x; + } + + // unragged. + }else{ + + current_y = block.y; + current_x = block.x; + for i in 0..lines.len(){ + + let mut line_word_len_without_space = 0; + + // count line word actually len (without spaces) + for j in 0..lines[i].content.len(){ + if !is_space(&(lines[i].content[j].text)){ + line_word_len_without_space += 1; + } + } + // non last line + if (i < lines.len() - 1) || (is_overflowed) { + + + let line_space_width = space_width + lines[i].div_text_offset / (line_word_len_without_space as f64 - 1.0); + + for j in 0..lines[i].content.len(){ + let con = lines[i].content[j]; + + + if is_space(&(con.text)){ + current_x += line_space_width; + }else{ + + layout_text(&(con.text), &font, current_x, current_y, &(block.color) , block.language, block.direction , &cxt); + current_x += con.width; + } + } + + current_y += block.lineskip; + current_x = block.x; + } + // last line and if it's not overflowed + else{ + for j in 0..lines[i].content.len(){ + let con = lines[i].content[j]; + + let mut content_width = con.width; + // if it's space, set it to space_width. + if is_space(&(con.text)){ + content_width = space_width; + } + + + layout_text(&(con.text), &font, current_x, current_y, &(block.color) , block.language, block.direction , &cxt); + current_x += content_width; + } + } + + } + + + } +} + +/// check if it's a space of not. +/// +/// +fn is_space(txt : &str) -> bool{ + return Regex::new(r"[ \t]+").unwrap().is_match(&txt) +} + +fn main(){ + let font_pt = 20; + + /*let font_name = "Amstelvar"; + let font_style = "Italic";*/ + + let font_name = "Noto Sans CJK TC"; + let font_style = "Light"; + + const PDF_WIDTH_IN_PX : f64 = 595.0; + const PDF_HEIGHT_IN_PX : f64 = 842.0; + let pdf_path = "/tmp/a.pdf"; + + let mut regex_pattern1 = r"([^\s\p{Bopomofo}\p{Han}\p{Hangul}\p{Hiragana}\p{Katakana}。,、;:「」『』()?!─……《》〈〉.~~゠‥{}[]〔〕〘〙〈〉《》【】〖〗※〳〵〴〲〱〽〃]{1,}|".to_string(); + let regex_pattern2 = r"[ \p{Bopomofo}\p{Han}\p{Hangul}\p{Hiragana}\p{Katakana}。,、;:「」『』()?!─……《》〈〉.~~゠‥{}[]〔〕〘〙〈〉《》【】〖〗※〳〵〴〲〱〽〃]|──|〴〵|〳〵|[ \t]+)"; + regex_pattern1.push_str(®ex_pattern2); + let regex_pattern = Regex::new(®ex_pattern1).unwrap(); + //let input_text = "我kā lí講這件——代誌彼は아버지 감사합니다といいます。It's true. happier. Ta̍k-ke. ٱلسَّلَامُ عَلَيْكُمْ שָׁלוֹם עֲלֵיכֶם"; + let input_text = "And why all this?d’aoís Certainly not because I believe that the land or the region has anything to do with it, for in any place and in any climate subjection is bitter and to be free is pleasant; but merely because I am of the opinion that one should pity those who, at birth, arrive with the yoke upon their necks. We should exonerate and forgive them, since they have not seen even the shadow of liberty, and, being quite unaware of it, cannot perceive the evil endured through their own slavery. If there were actually a country like that of the Cimmerians mentioned by Homer,"; + + // 翻譯:在主後1602年,戰爭爆發於兩個以之間——以.歐尼爾和以.如瓦.歐唐納,在金特塞里附近,那時愛爾蘭人民在戰場激烈的耗了九年,對抗他們的敵人,為了…… + + let mut input_text_vec = vec!(); + + let block = Div{x:100.0,y:100.0,width:450.0, height: 250.0, lineskip: 30.0, language: "en", direction: harfbuzz_rs::Direction::Ltr, color: "#198964".to_string()}; + + + let mut text : String; + for cap in regex_pattern.captures_iter(input_text){ + let text = cap[0].to_string().clone(); + + input_text_vec.push(text); + } + + + let mut font_struct1 = FontStruct{size:font_pt, name:font_name, style:font_style, variations : &[Variation::new(b"wght", 200.0), + Variation::new(b"wdth", 20.0)], features : &[]}; + + let box_coord_vec : Vec> = input_text_vec.into_iter().map(|x| get_box_cood_info(&x, font_struct1.name, font_struct1.style, font_struct1.size, block.language, harfbuzz_rs::Direction::Ltr, &[], &[])).collect(); + + + + + let surface = cairo::PdfSurface::new(PDF_WIDTH_IN_PX, PDF_HEIGHT_IN_PX, pdf_path).expect("Couldn’t create surface"); // A4 size + + let cxt = cairo::Context::new(&surface).expect("running error"); + + + + + cxt.set_source_rgba(0.8, 1.0, 1.0, 0.5); // 設定顏色 + cxt.paint().ok();// 設定背景顏色 + + cxt.set_source_rgba(0.0, 0.0, 1.0, 1.0); // 設定顏色 + + + let font_struct2 = FontStruct{size:30, name:"Noto Sans CJK TC", style:"Bold", variations : &[], features : &[]}; + + let font_struct3 = FontStruct{size:30, name:"Noto Nastaliq Urdu", style:"Bold", variations : &[], features : &[]}; + + //layout_text("Tá grá agam duit", font_struct1, 100.0, 100.0,"#198964",harfbuzz_rs::Direction::Ltr, &cxt); + + //layout_text("انا احبك ", &font_struct3, 100.0, 300.0,"#198964",harfbuzz_rs::Direction::Rtl, &cxt); + // println!("{:?}", result); + + greedy_typesetting(box_coord_vec, block, font_struct1, &cxt, false); +}