add tyoesetting languages.

This commit is contained in:
Tan, Kian-ting 2023-09-30 22:45:38 +08:00
parent 1b0fcba94e
commit c4b1175d9c
17 changed files with 944 additions and 0 deletions

BIN
ataabu-with-git.tar.gz Normal file

Binary file not shown.

BIN
tshunhue-with-git.tar.gz Normal file

Binary file not shown.

Binary file not shown.

View file

@ -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.

View file

@ -0,0 +1,2 @@
# tsulip
a little typesetter.

2
typesettings/tsulip/TODO Normal file
View file

@ -0,0 +1,2 @@
1. Token->Parser
2. main.rs 下面的「如何顯示文字於pdf的問題」

View file

@ -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<DataTokenAST>),
}
fn tokenizer(data: String) -> Vec<DataToken>{
// 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(&regex_string).unwrap();
let mut token_list : Vec<DataToken> = 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<DataToken>){
// 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 <yoxem.tem98@nctu.edu.tw>")
.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) ;
}

View file

@ -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();
}

Binary file not shown.

View file

@ -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)

Binary file not shown.

20
typesettings/uahli/.vscode/launch.json vendored Normal file
View file

@ -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",
}
]
}

10
typesettings/uahli/.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"command": "cargo",
"args": ["build"],
}
]
}

View file

@ -0,0 +1,13 @@
[package]
name = "uahli"
version = "0.1.0"
authors = ["Yoxem Chen <yoxem.tem98@nctu.edu.tw>"]
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"

View file

@ -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.

View file

@ -0,0 +1,2 @@
# uahli
typesetting toy

View file

@ -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<BoxCoodInfo>
/// - 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<u32, f64>,
hashmap_route: HashMap<u32, u32>,
words: String,
path: Vec<u32>
}
/*
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<Option<BoxCoodInfo>>, 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<BoxCoodInfo> {
// 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<x_offset{
box_cood.x_offset = x_offset
}
if box_cood.y_offset<y_offset{
box_cood.y_offset = y_offset
}
box_cood.width += x_advance;
box_cood.height += y_advance;
}
// convert to pt
box_cood.width;
box_cood.height;
box_cood.x_offset;
box_cood.y_offset;
return Some(box_cood);
}
///
///
/// converting font variant list to string. eg.
/// `font_variant_list_to_string(vec![Variation::new(b"wght", 800.0), Variation::new(b"wdth", 50.0)]);`
/// ==> `"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<RgbColor>{
let pattern = Regex::new(r"#(?P<r>[0-9a-fA-F]{2})(?P<g>[0-9a-fA-F]{2})(?P<b>[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<Option<BoxCoodInfo>>, 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(&regex_pattern2);
let regex_pattern = Regex::new(&regex_pattern1).unwrap();
//let input_text = "我kā lí講這件——代誌彼は아버지 감사합니다といいます。It's true. happier. Ta̍k-ke. ٱلسَّلَامُ عَلَيْكُمْ שָׁלוֹם עֲלֵיכֶם";
let input_text = "And why all this?daoí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<Option<BoxCoodInfo>> = 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("Couldnt 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);
}