{
+ let (tag_type, attr_list) = self.parse_tag_start(false)?;
+ if tag_type != TagType::Td {
+ return to_error(
+ format!("Expected , found {:?}", tag_type).as_str(),
+ );
+ }
+ let label = match self.tok.clone() {
+ Token::OpeningTag(TagType::Img) => {
+ self.mode = HtmlMode::HtmlTag;
+ let (tag_type, attr_list) = self.parse_tag_start(false)?;
+ if tag_type != TagType::Img {
+ return to_error(
+ format!("Expected , found {:?}", tag_type)
+ .as_str(),
+ );
+ }
+ let img = Image::from_tag_attr_list(attr_list)?;
+ LabelOrImg::Img(img)
+ }
+ _ => LabelOrImg::Html(self.parse_html_label()?),
+ };
+ self.parse_tag_end(&TagType::Td, true)?;
+ Ok(DotCell {
+ label,
+ td_attr: TdAttr::from_tag_attr_list(attr_list),
+ })
+ }
+
+ fn parse_tag_start(
+ &mut self,
+ pass_identifier: bool,
+ ) -> Result<(TagType, Vec<(String, String)>), String> {
+ let tag_type = if let Token::OpeningTag(x) = self.tok.clone() {
+ self.mode = HtmlMode::HtmlTag;
+ self.lex();
+ x
+ } else {
+ return to_error(
+ format!(
+ "Expected opening tag to start HTML label tag, found {:?}",
+ self.tok
+ )
+ .as_str(),
+ );
+ };
+ let tag_attr_list = self.parse_tag_attr_list(tag_type.clone())?;
+ match tag_type {
+ TagType::Br | TagType::Sub | TagType::Sup | TagType::S => {
+ // self.lexer.mode = super::lexer::HtmlMode::Html;
+ }
+ TagType::Hr
+ | TagType::Tr
+ | TagType::Td
+ | TagType::Table
+ | TagType::Img
+ | TagType::Vr
+ | TagType::Font
+ | TagType::I
+ | TagType::B
+ | TagType::U
+ | TagType::O => {
+ if pass_identifier {
+ if let Token::Identifier(_) = self.tok.clone() {
+ self.lex();
+ }
+ }
+ }
+ TagType::Unrecognized => {
+ return to_error(
+ format!("Unrecognized tag type {:?}", tag_type).as_str(),
+ );
+ }
+ }
+ Ok((tag_type, tag_attr_list))
+ }
+
+ fn parse_tag_end(
+ &mut self,
+ tag: &TagType,
+ pass_identifier: bool,
+ ) -> Result<(), String> {
+ if let Token::ClosingTag(x) = self.tok.clone() {
+ if x == *tag {
+ self.lex();
+ } else {
+ return to_error(
+ format!(
+ "Expected {:?} to end HTML label tag, found {:?}",
+ tag, x
+ )
+ .as_str(),
+ );
+ }
+ } else {
+ return to_error(format!("Expected 'closing tag {:?}' to end HTML label tag, found {:?}", tag, self.tok).as_str());
+ }
+ if pass_identifier {
+ if let Token::Identifier(_) = self.tok.clone() {
+ self.lex();
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Image {
+ fn from_tag_attr_list(
+ tag_attr_list: Vec<(String, String)>,
+ ) -> Result {
+ let mut scale = Scale::False;
+ let mut source = String::new();
+ for (key, value) in tag_attr_list.iter() {
+ match key.as_str() {
+ "scale" => {
+ scale = match value.as_str() {
+ "true" => Scale::True,
+ "width" => Scale::Width,
+ "height" => Scale::Height,
+ "both" => Scale::Both,
+ _ => Scale::False,
+ }
+ }
+ "src" => source = value.clone(),
+ _ => {}
+ }
+ }
+ Ok(Self { scale, source })
+ }
+
+ fn width(&self) -> f64 {
+ let size = get_image_size(&self.source).unwrap();
+ size.0 as f64
+ }
+ fn height(&self) -> f64 {
+ let size = get_image_size(&self.source).unwrap();
+ size.1 as f64
+ }
+
+ pub(crate) fn size(&self) -> Point {
+ let size = get_image_size(&self.source).unwrap();
+ Point::new(size.0 as f64, size.1 as f64)
+ }
+}
+
+impl Font {
+ fn new() -> Self {
+ Self {
+ color: None,
+ face: None,
+ point_size: None,
+ }
+ }
+
+ fn set_attr(&mut self, attr: &str, value: &str) {
+ match attr {
+ "color" => {
+ self.color = {
+ if let Some(color) = Color::from_name(value) {
+ Some(color)
+ } else {
+ None
+ }
+ }
+ }
+ "face" => self.face = Some(value.to_string()),
+ "point-size" => self.point_size = value.parse().ok(),
+ _ => {}
+ }
+ }
+
+ fn from_tag_attr_list(list: Vec<(String, String)>) -> Self {
+ let mut font = Self::new();
+ for (key, value) in list.iter() {
+ font.set_attr(key, value);
+ }
+ font
+ }
+}
+
+impl TextTag {
+ fn new(tag: &TagType, tag_attr_list: Vec<(String, String)>) -> Self {
+ match tag {
+ TagType::Font => {
+ let font = Font::from_tag_attr_list(tag_attr_list);
+ TextTag::Font(font)
+ }
+ TagType::I => TextTag::I,
+ TagType::B => TextTag::B,
+ TagType::U => TextTag::U,
+ TagType::O => TextTag::O,
+ TagType::Sub => TextTag::Sub,
+ TagType::Sup => TextTag::Sup,
+ TagType::S => TextTag::S,
+ _ => panic!("Invalid tag for text: {:?}", tag),
+ }
+ }
+}
+
+impl TableTag {
+ fn from_tag(tag_pair: Option<(TagType, Vec<(String, String)>)>) -> Self {
+ if let Some(tag_inner) = tag_pair {
+ match tag_inner.0 {
+ TagType::Table => TableTag::None,
+ TagType::Font => TableTag::Font(Font::from_tag_attr_list(
+ tag_inner.1.clone(),
+ )),
+ TagType::I => TableTag::I,
+ TagType::B => TableTag::B,
+ TagType::U => TableTag::U,
+ TagType::O => TableTag::O,
+ _ => panic!("Invalid tag for table: {:?}", tag_inner.0),
+ }
+ } else {
+ TableTag::None
+ }
+ }
+}
+
+impl TdAttr {
+ fn new() -> Self {
+ Self {
+ align: Align::Center,
+ balign: BAlign::Center,
+ bgcolor: None,
+ border: None,
+ cellpadding: None,
+ cellspacing: None,
+ color: None,
+ colspan: 1,
+ fixedsize: false,
+ gradientangle: None,
+ height: None,
+ href: None,
+ id: None,
+ port: None,
+ rowspan: 1,
+ sides: Sides::from_str(""),
+ style: None,
+ target: None,
+ title: None,
+ tooltip: None,
+ valign: VAlign::Middle,
+ width: None,
+ }
+ }
+
+ fn from_tag_attr_list(list: Vec<(String, String)>) -> Self {
+ let mut attr = Self::new();
+ for (key, value) in list.iter() {
+ attr.set_attr(key, value);
+ }
+ attr
+ }
+
+ fn set_attr(&mut self, attr: &str, value: &str) {
+ match attr {
+ "align" => {
+ self.align = match value {
+ "left" => Align::Left,
+ "right" => Align::Right,
+ _ => Align::Center,
+ }
+ }
+ "balign" => {
+ self.balign = match value {
+ "left" => BAlign::Left,
+ "right" => BAlign::Right,
+ _ => BAlign::Center,
+ }
+ }
+ "bgcolor" => self.bgcolor = Some(value.to_string()),
+ "border" => self.border = value.parse().ok(),
+ "cellpadding" => self.cellpadding = value.parse().ok(),
+ "cellspacing" => self.cellspacing = value.parse().ok(),
+ "color" => self.color = Some(value.to_string()),
+ "colspan" => self.colspan = value.parse().unwrap_or(1),
+ "fixedsize" => self.fixedsize = value == "true",
+ "gradientangle" => self.gradientangle = Some(value.to_string()),
+ "height" => self.height = value.parse().ok(),
+ "href" => self.href = Some(value.to_string()),
+ "id" => self.id = Some(value.to_string()),
+ "port" => self.port = Some(value.to_string()),
+ "rowspan" => self.rowspan = value.parse().unwrap_or(1),
+ "sides" => self.sides = Sides::from_str(value),
+ "style" => self.style = Some(value.to_string()),
+ "target" => self.target = Some(value.to_string()),
+ "title" => self.title = Some(value.to_string()),
+ "tooltip" => self.tooltip = Some(value.to_string()),
+ "valign" => {
+ self.valign = match value {
+ "top" => VAlign::Top,
+ "bottom" => VAlign::Bottom,
+ _ => VAlign::Middle,
+ }
+ }
+ "width" => self.width = value.parse().ok(),
+ _ => {}
+ }
+ }
+
+ pub(crate) fn build_style_attr(&self, style_attr: &StyleAttr) -> StyleAttr {
+ let mut style_attr = style_attr.clone();
+ if let Some(ref color) = self.bgcolor {
+ style_attr.fill_color = Color::from_name(color);
+ }
+ style_attr.valign = self.valign.clone();
+ style_attr.align = self.align.clone();
+ style_attr.balign = self.balign.clone();
+
+ style_attr
+ }
+}
+
+impl ColumnFormat {
+ fn from_str(s: &str) -> Self {
+ if s.starts_with('*') {
+ Self::Star
+ } else {
+ Self::None
+ }
+ }
+}
+
+impl RowFormat {
+ fn from_str(s: &str) -> Self {
+ if s.starts_with('*') {
+ Self::Star
+ } else {
+ Self::None
+ }
+ }
+}
+
+impl Sides {
+ fn from_str(s: &str) -> Self {
+ let mut sides = Self {
+ left: false,
+ right: false,
+ top: false,
+ bottom: false,
+ };
+ for c in s.chars() {
+ match c {
+ 'L' => sides.left = true,
+ 'R' => sides.right = true,
+ 'T' => sides.top = true,
+ 'B' => sides.bottom = true,
+ _ => {}
+ }
+ }
+ sides
+ }
+}
+
+impl TableAttr {
+ fn new() -> Self {
+ Self {
+ align: Align::Center,
+ bgcolor: None,
+ border: 1,
+ cellborder: None,
+ cellpadding: 2,
+ cellspacing: 2,
+ color: None,
+ columns: None,
+ fixedsize: false,
+ gradientangle: None,
+ height: None,
+ href: None,
+ id: None,
+ port: None,
+ rows: None,
+ sides: Sides::from_str(""),
+ style: None,
+ target: None,
+ title: None,
+ tooltip: None,
+ valign: VAlign::Middle,
+ width: None,
+ }
+ }
+ fn from_attr_list(list: Vec<(String, String)>) -> Self {
+ let mut attr = Self::new();
+ for (key, value) in list.iter() {
+ attr.set_attr(key, value);
+ }
+ attr
+ }
+
+ fn set_attr(&mut self, attr: &str, value: &str) {
+ let attr = attr.to_lowercase();
+ match attr.as_str() {
+ "align" => {
+ self.align = match value {
+ "left" => Align::Left,
+ "right" => Align::Right,
+ _ => Align::Center,
+ }
+ }
+ "bgcolor" => {
+ self.bgcolor = {
+ if let Some(color) = Color::from_name(value) {
+ Some(color)
+ } else {
+ None
+ }
+ }
+ }
+ "border" => self.border = value.parse().unwrap_or(0),
+ "cellborder" => self.cellborder = value.parse().ok(),
+ "cellpadding" => self.cellpadding = value.parse().unwrap_or(0),
+ "cellspacing" => self.cellspacing = value.parse().unwrap_or(0),
+ "color" => {
+ self.color = {
+ if let Some(color) = Color::from_name(value) {
+ Some(color)
+ } else {
+ None
+ }
+ }
+ }
+ "fixedsize" => self.fixedsize = value == "true",
+ "gradientangle" => self.gradientangle = Some(value.to_string()),
+ "height" => self.height = value.parse().ok(),
+ "width" => self.width = value.parse().ok(),
+ "href" => self.href = Some(value.to_string()),
+ "id" => self.id = Some(value.to_string()),
+ "port" => self.port = Some(value.to_string()),
+ "rows" => self.rows = Some(RowFormat::from_str(value)),
+ "sides" => self.sides = Sides::from_str(value),
+ "style" => self.style = Some(value.to_string()),
+ "target" => self.target = Some(value.to_string()),
+ "title" => self.title = Some(value.to_string()),
+ "tooltip" => self.tooltip = Some(value.to_string()),
+ "valign" => {
+ self.valign = match value {
+ "top" => VAlign::Top,
+ "bottom" => VAlign::Bottom,
+ _ => VAlign::Middle,
+ }
+ }
+ "columns" => self.columns = Some(ColumnFormat::from_str(value)),
+ _ => {}
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum HtmlGrid {
+ Text(TextGrid),
+ FontTable(TableGrid),
+}
+
+#[derive(Debug, Clone)]
+pub struct TextGrid {
+ // each line is a vector of PlainTextGrid
+ // as a whole it represent multiline text
+ pub(crate) text_items: Vec>,
+ br: Vec,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct PlainText {
+ pub(crate) text: String,
+ pub(crate) text_style: TextStyle,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct TextStyle {
+ pub(crate) font: Font,
+ pub(crate) font_style: FontStyle,
+ pub(crate) font_weight: FontWeight,
+ pub(crate) text_decoration: TextDecoration,
+ pub(crate) baseline_shift: BaselineShift,
+}
+
+#[derive(Debug, Clone)]
+pub struct TableGrid {
+ pub(crate) cells: Vec<(TdAttr, DotCellGrid)>,
+ grid: Vec>,
+ width_arr: Vec, // width in svg units
+ height_arr: Vec, // height in svg units
+ width_in_cell: usize, // width of the table in cells
+ height_in_cell: usize, // height of the table in cells
+ font_size: usize,
+ pub(crate) table_attr: TableAttr,
+ pub(crate) table_tag: TableTag,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct DotCellGrid {
+ i: usize,
+ j: usize,
+ width_in_cell: usize,
+ height_in_cell: usize,
+ pub(crate) label_grid: LabelOrImgGrid,
+ td_attr: TdAttr,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) enum LabelOrImgGrid {
+ Html(HtmlGrid),
+ Img(Image),
+}
+
+impl DotCellGrid {
+ fn from_dot_cell(
+ i: usize,
+ j: usize,
+ width_in_cell: usize,
+ height_in_cell: usize,
+ dot_cell: &DotCell,
+ ) -> Self {
+ let label_grid = match &dot_cell.label {
+ LabelOrImg::Html(html) => {
+ LabelOrImgGrid::Html(HtmlGrid::from_html(html))
+ }
+ LabelOrImg::Img(image) => LabelOrImgGrid::Img(image.clone()),
+ };
+ Self {
+ i,
+ j,
+ width_in_cell,
+ height_in_cell,
+ label_grid,
+ td_attr: dot_cell.td_attr.clone(),
+ }
+ }
+}
+
+impl TextGrid {
+ fn new() -> Self {
+ Self {
+ text_items: vec![],
+ br: vec![],
+ }
+ }
+ fn collect_from_text(&mut self, text: &Text, text_style: &TextStyle) {
+ for item in text.iter() {
+ match item {
+ TextItem::TaggedText(tagged_text) => {
+ let mut text_style = text_style.clone();
+ match &tagged_text.tag {
+ TextTag::Font(font) => {
+ text_style.font = font.clone();
+ }
+ TextTag::I => text_style.font_style = FontStyle::Italic,
+ TextTag::B => text_style.font_weight = FontWeight::Bold,
+ TextTag::U => {
+ text_style.text_decoration.underline = true
+ }
+ TextTag::O => {
+ text_style.text_decoration.overline = true
+ }
+ TextTag::Sub => {
+ text_style.baseline_shift = BaselineShift::Sub
+ }
+ TextTag::Sup => {
+ text_style.baseline_shift = BaselineShift::Super
+ }
+ TextTag::S => {
+ text_style.text_decoration.line_through = true
+ }
+ }
+ self.collect_from_text(
+ &tagged_text.text_items,
+ &text_style,
+ );
+ }
+ TextItem::Br(align) => {
+ self.text_items.push(vec![]);
+ self.br.push(align.clone());
+ }
+ TextItem::PlainText(text) => {
+ let plain_text = PlainText {
+ text: text.clone(),
+ text_style: text_style.clone(),
+ };
+ if let Some(last_line) = self.text_items.last_mut() {
+ last_line.push(plain_text);
+ } else {
+ let mut line = vec![];
+ line.push(plain_text);
+ self.text_items.push(line);
+ }
+ }
+ }
+ }
+ }
+
+ fn width(&self, font_size: usize) -> f64 {
+ let mut width = 0.0;
+ for line in self.text_items.iter() {
+ let mut line_width = 0.0;
+ for item in line.iter() {
+ let font_size = match item.text_style.font.point_size {
+ Some(size) => size as usize,
+ None => font_size,
+ };
+ let text_size = get_size_for_str(&item.text, font_size);
+ line_width += text_size.x;
+ }
+ if width < line_width {
+ width = line_width;
+ }
+ }
+ width
+ }
+ pub(crate) fn height(&self, font_size: usize) -> f64 {
+ let mut height = 0.0;
+ for line in self.text_items.iter() {
+ // TODO: we are going with the last with the assumption that heigh is the same for every plaintext,
+ // which is correct for the current get_size_for_str implementation
+ let mut line_height = 0.0;
+ for item in line.iter() {
+ let font_size = match item.text_style.font.point_size {
+ Some(size) => size as usize,
+ None => font_size,
+ };
+ let text_size = get_size_for_str(&item.text, font_size);
+ if line_height < text_size.y {
+ line_height = text_size.y;
+ }
+ }
+ height += line_height;
+ }
+ height
+ }
+}
+
+pub(crate) fn get_line_height(line: &Vec, font_size: usize) -> f64 {
+ let mut line_height = 0.0;
+ for item in line.iter() {
+ let font_size = match item.text_style.font.point_size {
+ Some(size) => size as usize,
+ None => font_size,
+ };
+ let text_size = get_size_for_str(&item.text, font_size);
+ if line_height < text_size.y {
+ line_height = text_size.y;
+ }
+ }
+ line_height
+}
+
+impl PlainText {
+ pub(crate) fn width(&self, font_size: usize) -> f64 {
+ let font_size = match self.text_style.font.point_size {
+ Some(size) => size as usize,
+ None => font_size,
+ };
+ get_size_for_str(&self.text, font_size).x
+ }
+
+ pub(crate) fn build_style_attr(&self, style_attr: &StyleAttr) -> StyleAttr {
+ let mut style_attr = style_attr.clone();
+ if let Some(font_size) = self.text_style.font.point_size {
+ style_attr.font_size = font_size as usize;
+ }
+
+ if let Some(ref color) = self.text_style.font.color {
+ style_attr.font_color = color.clone();
+ }
+
+ if let Some(ref face) = self.text_style.font.face {
+ style_attr.fontname = face.clone();
+ }
+
+ match self.text_style.font_style {
+ FontStyle::Italic => style_attr.font_style = FontStyle::Italic,
+ FontStyle::None => {}
+ }
+ match self.text_style.font_weight {
+ FontWeight::Bold => style_attr.font_weight = FontWeight::Bold,
+ FontWeight::None => {}
+ }
+ style_attr.text_decoration = self.text_style.text_decoration.clone();
+ style_attr.baseline_shift = self.text_style.baseline_shift.clone();
+
+ style_attr
+ }
+}
+
+impl HtmlGrid {
+ pub(crate) fn size(&self, font_size: usize) -> Point {
+ match self {
+ HtmlGrid::Text(text) => {
+ Point::new(text.width(font_size), text.height(font_size))
+ }
+ HtmlGrid::FontTable(table_grid) => table_grid.size(font_size),
+ }
+ }
+ fn from_html(html: &Html) -> Self {
+ match html {
+ Html::Text(text) => {
+ let mut text_grid = TextGrid::new();
+ let text_style = TextStyle {
+ font: Font::new(),
+ font_style: FontStyle::None,
+ font_weight: FontWeight::None,
+ text_decoration: TextDecoration::new(),
+ baseline_shift: BaselineShift::Normal,
+ };
+ text_grid.collect_from_text(text, &text_style);
+ HtmlGrid::Text(text_grid)
+ }
+ Html::FontTable(table) => {
+ HtmlGrid::FontTable(TableGrid::from_table(table))
+ }
+ }
+ }
+}
+
+fn is_text_table_wrapper_invalid(text: &str) -> bool {
+ for line in text.lines() {
+ if !line.is_empty() {
+ return true;
+ }
+ }
+ false
+}
+
+#[derive(Debug, Clone)]
+struct TableHashGrid {
+ pub(crate) cells: Vec<(TdAttr, DotCellGrid)>,
+ pub(crate) occupation: HashMap<(usize, usize), usize>, // x, y, cell index
+}
+
+impl TableHashGrid {
+ fn width(&self) -> usize {
+ self.occupation.keys().map(|(x, _)| *x).max().unwrap_or(0) + 1
+ }
+ fn height(&self) -> usize {
+ self.occupation.keys().map(|(_, y)| *y).max().unwrap_or(0) + 1
+ }
+ // fn pretty_print(&self) {
+ // // print in a table format with + indicating occupied and - indicating free
+ // let width = self.width();
+ // let height = self.height();
+ // let mut table = vec![vec!['-'; width]; height];
+ // for (x, y) in self.occupation.keys() {
+ // table[*y][*x] = '+';
+ // }
+ // for y in 0..height {
+ // for x in 0..width {
+ // print!("{}", table[y][x]);
+ // }
+ // println!();
+ // }
+ // }
+ fn add_cell(
+ &mut self,
+ x: usize,
+ y: usize,
+ width: usize,
+ height: usize,
+ td_attr: TdAttr,
+ dot_cell: DotCell,
+ ) {
+ self.cells.push((
+ td_attr,
+ DotCellGrid::from_dot_cell(x, y, width, height, &dot_cell),
+ ));
+ // boundaries are desinged with respect to html specs for forming table algo
+ for i in x..(x + width) {
+ for j in y..(y + height) {
+ let x = self.occupation.insert((i, j), self.cells.len() - 1);
+ if x.is_some() {
+ panic!("Cell already occupied at ({}, {})", i, j);
+ }
+ }
+ }
+ }
+ fn is_occupied(&self, x: usize, y: usize) -> bool {
+ self.occupation.contains_key(&(x, y))
+ }
+
+ fn from_table(font_table: &FontTable) -> Self {
+ let mut width = 0;
+ let mut height = 0;
+ let mut y_current = 0;
+ let mut table_grid = Self {
+ cells: Vec::new(),
+ occupation: HashMap::new(),
+ };
+ for row in &font_table.rows {
+ table_grid.process_row(
+ &row.0,
+ &mut width,
+ &mut height,
+ &mut y_current,
+ );
+ }
+
+ // ending part
+ table_grid
+ }
+
+ fn process_row(
+ &mut self,
+ row: &Row,
+ width: &mut usize,
+ height: &mut usize,
+ y_current: &mut usize,
+ ) {
+ if height == y_current {
+ *height += 1;
+ }
+ let mut x_current = 0;
+ for c in &row.cells {
+ let cell = &c.0;
+ let colspan = cell.td_attr.colspan as usize;
+ let rowspan = cell.td_attr.rowspan as usize;
+ while x_current < *width && self.is_occupied(x_current, *y_current)
+ {
+ x_current += 1;
+ }
+ if x_current == *width {
+ *width += 1;
+ }
+ if *width < x_current + colspan {
+ *width = x_current + colspan;
+ }
+ if *height < *y_current + rowspan {
+ *height = *y_current + rowspan;
+ }
+
+ self.add_cell(
+ x_current,
+ *y_current,
+ colspan,
+ rowspan,
+ cell.td_attr.clone(),
+ cell.clone(),
+ );
+ x_current += colspan;
+ }
+ *y_current += 1;
+ }
+}
+
+impl TableGrid {
+ pub(crate) fn width(&self) -> f64 {
+ self.width_arr.iter().sum::()
+ + (self.table_attr.cellspacing as usize * (self.width_in_cell + 1))
+ as f64
+ + self.table_attr.border as f64 * 2.
+ }
+ pub(crate) fn height(&self) -> f64 {
+ self.height_arr.iter().sum::()
+ + (self.table_attr.cellspacing as usize * (self.height_in_cell + 1))
+ as f64
+ + self.table_attr.border as f64 * 2.
+ }
+ fn size(&self, font_size: usize) -> Point {
+ if font_size != self.font_size {
+ let mut table_grid = self.clone();
+ table_grid.resize(font_size);
+ Point::new(table_grid.width(), table_grid.height())
+ } else {
+ Point::new(self.width(), self.height())
+ }
+ }
+ pub(crate) fn cell_pos(&self, d: &DotCellGrid) -> Point {
+ let idx = d.i;
+ let x = self.width_arr.iter().take(idx).sum::()
+ + (self.table_attr.cellspacing as usize * (idx + 1)) as f64
+ + self.table_attr.border as f64 / 2.0;
+
+ let idx = d.j;
+
+ let y = self.height_arr.iter().take(idx).sum::()
+ + (self.table_attr.cellspacing as usize * (idx + 1)) as f64
+ + self.table_attr.border as f64 / 2.0;
+
+ Point::new(x, y)
+ }
+ pub(crate) fn cell_size(&self, dot_cell_grid: &DotCellGrid) -> Point {
+ let mut height = 0f64;
+ for i in
+ dot_cell_grid.j..(dot_cell_grid.j + dot_cell_grid.height_in_cell)
+ {
+ height += self.height_arr[i];
+ }
+ height += self.table_attr.cellspacing as f64
+ * (dot_cell_grid.height_in_cell as f64 - 1.);
+
+ let mut width = 0f64;
+ for i in
+ dot_cell_grid.i..(dot_cell_grid.i + dot_cell_grid.width_in_cell)
+ {
+ width += self.width_arr[i];
+ }
+ width += self.table_attr.cellspacing as f64
+ * (dot_cell_grid.width_in_cell as f64 - 1.);
+
+ Point::new(width, height)
+ }
+
+ fn from_table(font_table: &FontTable) -> Self {
+ let table_hash_grid = TableHashGrid::from_table(font_table);
+ let width_in_cell = table_hash_grid.width();
+ let height_in_cell = table_hash_grid.height();
+ let mut grid = vec![None; width_in_cell * height_in_cell];
+ for (idx, (_td_attr, dot_cell)) in
+ table_hash_grid.cells.iter().enumerate()
+ {
+ for i in 0..dot_cell.width_in_cell {
+ for j in 0..dot_cell.height_in_cell {
+ let x_cur = dot_cell.i + i;
+ let y_cur = dot_cell.j + j;
+ grid[(y_cur * width_in_cell) + x_cur] = Some(idx);
+ }
+ }
+ }
+
+ Self {
+ cells: table_hash_grid.cells,
+ grid,
+ width_arr: vec![1.0; width_in_cell],
+ height_arr: vec![1.0; height_in_cell],
+ width_in_cell,
+ height_in_cell,
+ font_size: 0,
+ table_attr: font_table.table_attr.clone(),
+ table_tag: font_table.tag.clone(),
+ }
+ }
+
+ fn get_cell(&self, i: usize, j: usize) -> Option<&DotCellGrid> {
+ if i < self.width_in_cell && j < self.height_in_cell {
+ let index = self.grid[(j * (self.width_in_cell)) + i];
+ if let Some(i) = index {
+ return Some(&self.cells[i].1);
+ }
+ }
+ None
+ }
+
+ fn get_cell_mut(&mut self, i: usize, j: usize) -> Option<&mut DotCellGrid> {
+ if i < self.width_in_cell && j < self.height_in_cell {
+ let index = self.grid[(j * (self.width_in_cell)) + i];
+ if let Some(i) = index {
+ return Some(&mut self.cells[i].1);
+ }
+ }
+ None
+ }
+
+ pub(crate) fn cellpadding(&self, d: &DotCellGrid) -> f64 {
+ let cellpadding = if let Some(td_cellpadding) = d.td_attr.cellpadding {
+ td_cellpadding
+ } else {
+ self.table_attr.cellpadding
+ } as f64;
+
+ cellpadding
+ }
+
+ pub(crate) fn cellborder(&self, d: &DotCellGrid) -> f64 {
+ let cellborder = if let Some(td_cellborder) = d.td_attr.border {
+ td_cellborder
+ } else if let Some(td_cellborder) = self.table_attr.cellborder {
+ td_cellborder
+ } else {
+ self.table_attr.border
+ } as f64;
+
+ cellborder
+ }
+
+ pub(crate) fn resize(&mut self, font_size: usize) {
+ // TODO: can check if font size is updated
+ for x in 0..self.width_in_cell {
+ let mut max_width = 0f64;
+ for y in 0..self.height_in_cell {
+ if let Some(cell) = self.get_cell_mut(x, y) {
+ match &mut cell.label_grid {
+ LabelOrImgGrid::Html(HtmlGrid::FontTable(x)) => {
+ x.resize(font_size);
+ }
+ _ => {}
+ }
+ }
+ }
+ for y in 0..self.height_in_cell {
+ if let Some(cell) = self.get_cell(x, y) {
+ let w = match &cell.label_grid {
+ LabelOrImgGrid::Html(html) => match html {
+ HtmlGrid::Text(text) => text.width(font_size),
+ HtmlGrid::FontTable(x) => x.width(),
+ },
+ LabelOrImgGrid::Img(img) => img.width(),
+ };
+ let cellpadding = self.cellpadding(cell);
+ let cellborder = self.cellborder(cell);
+
+ let w = w + cellborder * 2.0 + cellpadding * 2.0;
+
+ max_width = max_width.max(w / cell.width_in_cell as f64);
+ }
+ }
+
+ self.width_arr[x] = max_width;
+ }
+
+ for y in 0..self.height_in_cell {
+ let mut max_height = 0f64;
+ for x in 0..self.width_in_cell {
+ if let Some(cell) = self.get_cell(x, y) {
+ let h = match &cell.label_grid {
+ LabelOrImgGrid::Html(html) => match html {
+ HtmlGrid::Text(text) => text.height(font_size),
+ HtmlGrid::FontTable(x) => x.height(),
+ },
+ LabelOrImgGrid::Img(img) => img.height(),
+ };
+ let cellpadding = self.cellpadding(cell);
+ let cellborder = self.cellborder(cell);
+
+ let h = h + cellborder * 2.0 + cellpadding * 2.0;
+
+ max_height = max_height.max(h / cell.height_in_cell as f64);
+ }
+ }
+ self.height_arr[y] = max_height;
+ }
+
+ // update the font size
+ self.font_size = font_size;
+ }
+
+ pub(crate) fn build_style_attr(&self, style_attr: &StyleAttr) -> StyleAttr {
+ let mut style_attr = style_attr.clone();
+ if let Some(ref color) = self.table_attr.bgcolor {
+ style_attr.fill_color = Some(color.clone());
+ }
+ style_attr.valign = self.table_attr.valign.clone();
+ style_attr.align = self.table_attr.align.clone();
+ style_attr.line_width = self.table_attr.border as usize;
+
+ match &self.table_tag {
+ TableTag::B => {
+ style_attr.font_weight = FontWeight::Bold;
+ }
+ TableTag::I => {
+ style_attr.font_style = FontStyle::Italic;
+ }
+ TableTag::U => {
+ style_attr.text_decoration.underline = true;
+ }
+ TableTag::O => {
+ style_attr.text_decoration.overline = true;
+ }
+ TableTag::Font(font) => {
+ if let Some(point_size) = font.point_size {
+ style_attr.font_size = point_size as usize;
+ }
+ if let Some(font_color) = font.color {
+ style_attr.font_color = font_color;
+ }
+ if let Some(ref font_name) = font.face {
+ style_attr.fontname = font_name.clone();
+ }
+ }
+ TableTag::None => {}
+ }
+
+ style_attr
+ }
+}
diff --git a/layout/src/gv/mod.rs b/layout/src/gv/mod.rs
index 2dc4acb..8bc4a48 100644
--- a/layout/src/gv/mod.rs
+++ b/layout/src/gv/mod.rs
@@ -2,6 +2,7 @@
//! file format (parsing, building a compatible graph, etc.)
pub mod builder;
+pub(crate) mod html;
pub mod parser;
pub mod record;
diff --git a/layout/src/gv/parser/ast.rs b/layout/src/gv/parser/ast.rs
index 8caff9d..ff56737 100644
--- a/layout/src/gv/parser/ast.rs
+++ b/layout/src/gv/parser/ast.rs
@@ -15,21 +15,41 @@ impl NodeId {
}
}
+#[derive(Debug, Clone)]
+pub enum DotString {
+ String(String),
+ HtmlString(String),
+}
+
+impl std::fmt::Display for DotString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::String(x) => write!(f, "{}", x),
+ Self::HtmlString(_) => write!(f, "htmlstring"),
+ }
+ }
+}
+
// [a=b; c=d; ... ]
#[derive(Debug, Clone)]
pub struct AttributeList {
- pub list: Vec<(String, String)>,
+ pub list: Vec<(String, DotString)>,
}
impl AttributeList {
pub fn new() -> Self {
Self { list: Vec::new() }
}
- pub fn add_attr(&mut self, from: &str, to: &str) {
- self.list.push((from.to_string(), to.to_string()));
+ pub fn add_attr_str(&mut self, from: &str, to: &str) {
+ self.list
+ .push((from.to_string(), DotString::String(to.to_string())));
+ }
+
+ pub fn add_attr(&mut self, from: String, to: DotString) {
+ self.list.push((from, to));
}
- pub fn iter(&self) -> std::slice::Iter<(String, String)> {
+ pub fn iter(&self) -> std::slice::Iter<(String, DotString)> {
self.list.iter()
}
}
diff --git a/layout/src/gv/parser/lexer.rs b/layout/src/gv/parser/lexer.rs
index aa894de..a32fbe0 100644
--- a/layout/src/gv/parser/lexer.rs
+++ b/layout/src/gv/parser/lexer.rs
@@ -18,12 +18,14 @@ pub enum Token {
ArrowLine,
OpenBracket,
CloseBracket,
+ HtmlStart,
+ HtmlEnd,
OpenBrace,
CloseBrace,
Error(usize),
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Lexer {
input: Vec,
pub pos: usize,
@@ -184,6 +186,29 @@ impl Lexer {
Token::Identifier(result)
}
+ pub fn next_token_html(&mut self) -> Token {
+ let mut result = String::new();
+ let mut bracket_balance = 1;
+ loop {
+ // Handle escaping
+ if self.ch == '\0' {
+ // Reached EOF without completing the string
+ return Token::Error(self.pos);
+ }
+ if self.ch == '<' {
+ bracket_balance += 1;
+ } else if self.ch == '>' {
+ if bracket_balance == 1 {
+ break;
+ }
+ bracket_balance -= 1;
+ }
+ result.push(self.ch);
+ self.read_char();
+ }
+ Token::Identifier(result)
+ }
+
pub fn next_token(&mut self) -> Token {
let tok: Token;
while self.skip_comment() || self.skip_whitespace() {}
@@ -215,6 +240,12 @@ impl Lexer {
'"' => {
tok = self.read_string();
}
+ '<' => {
+ tok = Token::HtmlStart;
+ }
+ '>' => {
+ tok = Token::HtmlEnd;
+ }
'-' => {
self.read_char();
match self.ch {
diff --git a/layout/src/gv/parser/parser.rs b/layout/src/gv/parser/parser.rs
index c61280f..a3dcd61 100644
--- a/layout/src/gv/parser/parser.rs
+++ b/layout/src/gv/parser/parser.rs
@@ -1,8 +1,9 @@
use super::ast;
use super::lexer::Lexer;
use super::lexer::Token;
+use crate::gv::parser::ast::DotString;
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct DotParser {
lexer: Lexer,
tok: Token,
@@ -40,6 +41,20 @@ impl DotParser {
}
}
}
+ pub fn lex_html(&mut self) {
+ match self.tok {
+ Token::Error(_) => {
+ panic!("can't parse after error");
+ }
+ Token::EOF => {
+ panic!("can't parse after EOF");
+ }
+ _ => {
+ // Lex the next token.
+ self.tok = self.lexer.next_token_html();
+ }
+ }
+ }
// graph : [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}'
//subgraph : [ subgraph [ ID ] ] '{' stmt_list '}'
@@ -141,8 +156,17 @@ impl DotParser {
Result::Ok(ast::Stmt::Edge(es))
}
Token::Equal => {
- let es = self.parse_attribute_stmt(id0)?;
- Result::Ok(ast::Stmt::Attribute(es))
+ if id0.port.is_some() {
+ return to_error("Can't assign into a port");
+ }
+ self.lex();
+ let es = self.parse_attr_id()?;
+ let mut list = ast::AttributeList::new();
+ list.add_attr(id0.name, es);
+ Result::Ok(ast::Stmt::Attribute(ast::AttrStmt::new(
+ ast::AttrStmtTarget::Graph,
+ list,
+ )))
}
Token::Identifier(_) => {
let ns = ast::NodeStmt::new(id0);
@@ -232,14 +256,8 @@ impl DotParser {
} else {
return to_error("Expected '='");
}
-
- if let Token::Identifier(value) = self.tok.clone() {
- lst.add_attr(&prop, &value);
- // Consume the value name.
- self.lex();
- } else {
- return to_error("Expected value after assignment");
- }
+ let value = self.parse_attr_id()?;
+ lst.add_attr(prop, value);
// Skip semicolon.
if let Token::Semicolon = self.tok.clone() {
@@ -257,36 +275,46 @@ impl DotParser {
}
Result::Ok(lst)
}
+ // Parses a string that is inside a HTML tag.
+ pub fn parse_html_string(&mut self) -> Result {
+ self.lex_html();
+ if let Token::Identifier(s) = self.tok.clone() {
+ self.lex();
+ Ok(s)
+ } else {
+ to_error("Expected a string")
+ }
+ }
fn is_edge_token(&self) -> bool {
matches!(self.tok, Token::ArrowLine | Token::ArrowRight)
}
// ID '=' ID
- pub fn parse_attribute_stmt(
- &mut self,
- id: ast::NodeId,
- ) -> Result {
- let mut lst = ast::AttributeList::new();
-
- if id.port.is_some() {
- return to_error("Can't assign into a port");
- }
-
- if let Token::Equal = self.tok.clone() {
- self.lex();
- } else {
- return to_error("Expected '='");
- }
-
- if let Token::Identifier(val) = self.tok.clone() {
- lst.add_attr(&id.name, &val);
+ pub fn parse_attr_id(&mut self) -> Result {
+ if let Token::HtmlStart = self.tok.clone() {
+ let html = self.parse_html_string()?;
+ if let Token::HtmlEnd = self.tok.clone() {
+ self.lex();
+ } else {
+ return to_error(
+ format!("Expected '>', found {:?}", self.tok).as_str(),
+ );
+ }
+ Result::Ok(DotString::HtmlString(html))
+ } else if let Token::Identifier(value) = self.tok.clone() {
+ // Consume the value name.
self.lex();
+ Result::Ok(DotString::String(value))
} else {
- return to_error("Expected identifier.");
+ to_error(
+ format!(
+ "Expected value after assignment, found {:?}",
+ self.tok
+ )
+ .as_str(),
+ )
}
-
- Result::Ok(ast::AttrStmt::new(ast::AttrStmtTarget::Graph, lst))
}
//edge_stmt : (node_id | subgraph) edgeRHS [ attr_list ]
diff --git a/layout/src/gv/parser/printer.rs b/layout/src/gv/parser/printer.rs
index 75007dd..601c713 100644
--- a/layout/src/gv/parser/printer.rs
+++ b/layout/src/gv/parser/printer.rs
@@ -1,6 +1,6 @@
//! A collection of methods for printing the AST.
-use super::ast;
+use super::ast::{self, DotString};
fn print_node_id(n: &ast::NodeId, indent: usize) {
print!("{}", " ".repeat(indent));
@@ -21,7 +21,7 @@ fn print_arrow(k: &ast::ArrowKind, indent: usize) {
}
}
}
-fn print_attribute(a: &str, b: &str, indent: usize, i: usize) {
+fn print_attribute(a: &str, b: &DotString, indent: usize, i: usize) {
print!("{}", " ".repeat(indent));
println!("{})\"{}\" = \"{}\"", i, a, b);
}
diff --git a/layout/src/std_shapes/render.rs b/layout/src/std_shapes/render.rs
index b818278..15afae0 100644
--- a/layout/src/std_shapes/render.rs
+++ b/layout/src/std_shapes/render.rs
@@ -3,7 +3,11 @@
use crate::core::base::Orientation;
use crate::core::format::{ClipHandle, RenderBackend, Renderable, Visible};
use crate::core::geometry::*;
-use crate::core::style::{LineStyleKind, StyleAttr};
+use crate::core::style::{Align, LineStyleKind, StyleAttr, VAlign};
+use crate::gv::html::{
+ get_line_height, DotCellGrid, HtmlGrid, LabelOrImgGrid, Scale, TableGrid,
+ TextGrid,
+};
use crate::std_shapes::shapes::*;
/// Return the height and width of the record, depending on the geometry and
@@ -39,6 +43,13 @@ fn get_record_size(
const BOX_SHAPE_PADDING: f64 = 10.;
const CIRCLE_SHAPE_PADDING: f64 = 20.;
+fn get_size_for_content(content: &ShapeContent, font: usize) -> Point {
+ match content {
+ ShapeContent::String(s) => get_size_for_str(s, font),
+ ShapeContent::Html(html) => html.size(font),
+ }
+}
+
/// Return the size of the shape. If \p make_xy_same is set then make the
/// X and the Y of the shape the same. This will turn ellipses into circles and
/// rectangles into boxes. The parameter \p dir specifies the direction of the
@@ -50,29 +61,35 @@ pub fn get_shape_size(
make_xy_same: bool,
) -> Point {
let mut res = match s {
- ShapeKind::Box(text) => {
- pad_shape_scalar(get_size_for_str(text, font), BOX_SHAPE_PADDING)
- }
- ShapeKind::Circle(text) => {
- pad_shape_scalar(get_size_for_str(text, font), CIRCLE_SHAPE_PADDING)
- }
- ShapeKind::DoubleCircle(text) => {
- pad_shape_scalar(get_size_for_str(text, font), CIRCLE_SHAPE_PADDING)
- }
+ ShapeKind::Box(text) => pad_shape_scalar(
+ get_size_for_content(text, font),
+ BOX_SHAPE_PADDING,
+ ),
+ ShapeKind::Circle(text) => pad_shape_scalar(
+ get_size_for_content(text, font),
+ CIRCLE_SHAPE_PADDING,
+ ),
+ ShapeKind::DoubleCircle(text) => pad_shape_scalar(
+ get_size_for_content(text, font),
+ CIRCLE_SHAPE_PADDING,
+ ),
ShapeKind::Record(sr) => {
pad_shape_scalar(get_record_size(sr, dir, font), BOX_SHAPE_PADDING)
}
ShapeKind::Connector(text) => {
if let Option::Some(text) = text {
pad_shape_scalar(
- get_size_for_str(text, font),
+ get_size_for_content(text, font),
BOX_SHAPE_PADDING,
)
} else {
Point::new(1., 1.)
}
}
- _ => Point::new(1., 1.),
+ ShapeKind::None(text) => pad_shape_scalar(
+ get_size_for_content(text, font),
+ BOX_SHAPE_PADDING,
+ ),
};
if make_xy_same {
res = make_size_square(res);
@@ -122,6 +139,70 @@ fn get_record_port_location(
visit_record(rec, dir, loc, size, look, &mut visitor);
(visitor.loc, visitor.size)
}
+struct Locator {
+ port_name: String,
+ loc: Point,
+ size: Point,
+}
+fn get_html_port_location(
+ html: &HtmlGrid,
+ loc: Point,
+ size: Point,
+ visitor: &mut Locator,
+) -> (Point, Point) {
+ match html {
+ HtmlGrid::Text(_text) => {}
+ HtmlGrid::FontTable(table) => {
+ get_table_port_location(table, loc, size, visitor)
+ }
+ }
+ (visitor.loc, visitor.size)
+}
+
+fn get_table_port_location(
+ table: &TableGrid,
+ loc: Point,
+ _size: Point,
+ visitor: &mut Locator,
+) {
+ if let Some(ref port_name) = table.table_attr.port {
+ if port_name == &visitor.port_name {
+ visitor.loc = loc;
+ visitor.size = Point::new(table.width(), table.height());
+ }
+ }
+ let table_width = table.width();
+ let table_height = table.height();
+ for (td_attr, c) in table.cells.iter() {
+ let cell_size = table.cell_size(c);
+ let cell_origin = table.cell_pos(c);
+ let cell_loc = Point::new(
+ visitor.loc.x + cell_origin.x + cell_size.x * 0.5
+ - table_width * 0.5,
+ visitor.loc.y + cell_origin.y + cell_size.y * 0.5
+ - table_height * 0.5,
+ );
+ if let Option::Some(ref port_name) = td_attr.port {
+ if port_name == &visitor.port_name {
+ visitor.loc = cell_loc;
+ visitor.size = cell_size;
+ }
+ }
+
+ get_cell_port_location(&c, cell_loc, cell_size, visitor);
+ }
+}
+
+fn get_cell_port_location(
+ rec: &DotCellGrid,
+ loc: Point,
+ size: Point,
+ visitor: &mut Locator,
+) {
+ if let LabelOrImgGrid::Html(html) = &rec.label_grid {
+ get_html_port_location(html, loc, size, visitor);
+ }
+}
fn render_record(
rec: &RecordDef,
@@ -187,6 +268,191 @@ fn render_record(
);
}
+fn render_html(
+ rec: &HtmlGrid,
+ loc: Point,
+ size: Point,
+ look: &StyleAttr,
+ canvas: &mut dyn RenderBackend,
+) {
+ match rec {
+ HtmlGrid::Text(text) => {
+ render_text(text, loc, size, look, canvas);
+ }
+ HtmlGrid::FontTable(table) => {
+ render_font_table(table, loc, look, canvas);
+ }
+ }
+}
+
+fn update_location(
+ loc: Point,
+ size: Point,
+ text: &str,
+ look: &StyleAttr,
+) -> Point {
+ let mut loc = loc;
+ let text_size = get_size_for_str(text, look.font_size);
+ let displacement = size.sub(text_size);
+ match look.align {
+ Align::Left => {
+ loc.x -= displacement.x / 2.;
+ }
+ Align::Right => {
+ loc.x += displacement.x / 2.;
+ }
+ Align::Center => {}
+ }
+ match look.valign {
+ VAlign::Top => {
+ loc.y -= displacement.y / 2.;
+ }
+ VAlign::Bottom => {
+ loc.y += displacement.y / 2.;
+ }
+ VAlign::Middle => {}
+ }
+ loc
+}
+
+fn render_text(
+ rec: &TextGrid,
+ loc: Point,
+ size: Point,
+ look: &StyleAttr,
+ canvas: &mut dyn RenderBackend,
+) {
+ let loc_0_x = loc.x;
+ let mut loc = loc;
+
+ loc.y -= rec.height(look.font_size) / 2.;
+
+ for line in &rec.text_items {
+ let mut line_width = 0.;
+ for t in line {
+ line_width += t.width(look.font_size);
+ }
+ loc.x = loc_0_x - line_width / 2.;
+ for t in line {
+ let look_text = t.build_style_attr(look);
+ let text_size = get_size_for_str(&t.text, look_text.font_size);
+ loc.x += text_size.x / 2.;
+ let loc_text = update_location(loc, size, &t.text, &look_text);
+ canvas.draw_text(loc_text, t.text.as_str(), &look_text);
+ loc.x += text_size.x / 2.;
+ }
+ loc.y += get_line_height(line, look.font_size);
+ }
+}
+
+fn render_font_table(
+ rec: &TableGrid,
+ loc: Point,
+ look: &StyleAttr,
+ canvas: &mut dyn RenderBackend,
+) {
+ let look = rec.build_style_attr(look);
+ let table_grid_width = rec.width();
+ let table_grid_height = rec.height();
+
+ // top left origin location of the table
+ let loc_0 = Point::new(
+ loc.x - table_grid_width / 2.,
+ loc.y - table_grid_height / 2.,
+ );
+ canvas.draw_rect(
+ loc_0,
+ Point::new(
+ table_grid_width - rec.table_attr.border as f64,
+ table_grid_height - rec.table_attr.border as f64,
+ ),
+ &look,
+ Option::None,
+ Option::None,
+ );
+
+ for (td_attr, c) in rec.cells.iter() {
+ let cellpadding = rec.cellpadding(c);
+ let cellborder = rec.cellborder(c);
+ let cell_size = rec.cell_size(c);
+ let cell_origin = rec.cell_pos(c);
+
+ // center of the cell
+ let cell_loc = Point::new(
+ loc_0.x + cell_origin.x + cell_size.x * 0.5,
+ loc_0.y + cell_origin.y + cell_size.y * 0.5,
+ );
+ let look_cell = td_attr.build_style_attr(&look);
+
+ let mut look_cell_border = look.clone();
+ look_cell_border.line_width = cellborder as usize;
+
+ canvas.draw_rect(
+ Point::new(
+ loc_0.x + cell_origin.x + cellborder * 0.5,
+ loc_0.y + cell_origin.y + cellborder * 0.5,
+ ),
+ cell_size.sub(Point::splat(cellborder)),
+ &look_cell_border,
+ Option::None,
+ Option::None,
+ );
+
+ // cell inside
+ let size = Point::new(
+ cell_size.x - cellborder * 2. - cellpadding * 2.,
+ cell_size.y - cellborder * 2. - cellpadding * 2.,
+ );
+ render_cell(&c, cell_loc, size, &look_cell, canvas);
+ }
+}
+
+fn render_cell(
+ rec: &DotCellGrid,
+ loc: Point,
+ size: Point,
+ look: &StyleAttr,
+ canvas: &mut dyn RenderBackend,
+) {
+ match &rec.label_grid {
+ LabelOrImgGrid::Html(html) => {
+ render_html(html, loc, size, look, canvas);
+ }
+ LabelOrImgGrid::Img(img) => {
+ // TODO: Need to introduce setting to control file access as specificed by ofifical graphviz source
+ let image_size = img.size();
+ let image_size = match &img.scale {
+ Scale::False => Point::new(image_size.x, image_size.y),
+ Scale::True => {
+ let x_scale = size.x / image_size.x;
+ let y_scale = size.y / image_size.y;
+ let scale =
+ if x_scale < y_scale { x_scale } else { y_scale };
+ Point::new(image_size.x * scale, image_size.y * scale)
+ }
+ Scale::Width => {
+ let scale = size.x / image_size.x;
+ Point::new(image_size.x * scale, image_size.y)
+ }
+ Scale::Height => {
+ let scale = size.y / image_size.y;
+ Point::new(image_size.x, image_size.y * scale)
+ }
+ Scale::Both => size.clone(),
+ };
+ canvas.draw_image(
+ Point::new(
+ loc.x - image_size.x / 2.,
+ loc.y - image_size.y / 2.,
+ ),
+ image_size,
+ &img.source,
+ None,
+ );
+ }
+ }
+}
+
pub trait RecordVisitor {
fn handle_box(&mut self, loc: Point, size: Point);
fn handle_text(
@@ -272,6 +538,23 @@ fn visit_record(
}
}
+fn draw_shape_content(
+ content: &ShapeContent,
+ loc: Point,
+ size: Point,
+ look: &StyleAttr,
+ canvas: &mut dyn RenderBackend,
+) {
+ match content {
+ ShapeContent::String(text) => {
+ canvas.draw_text(loc, text.as_str(), look);
+ }
+ ShapeContent::Html(html) => {
+ render_html(html, loc, size, look, canvas);
+ }
+ }
+}
+
impl Renderable for Element {
fn render(&self, debug: bool, canvas: &mut dyn RenderBackend) {
if debug {
@@ -288,7 +571,15 @@ impl Renderable for Element {
}
match &self.shape {
- ShapeKind::None => {}
+ ShapeKind::None(text) => {
+ draw_shape_content(
+ text,
+ self.pos.center(),
+ self.pos.size(false),
+ &self.look,
+ canvas,
+ );
+ }
ShapeKind::Record(rec) => {
render_record(
rec,
@@ -307,7 +598,13 @@ impl Renderable for Element {
self.properties.clone(),
Option::None,
);
- canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
+ draw_shape_content(
+ text,
+ self.pos.center(),
+ self.pos.size(false),
+ &self.look,
+ canvas,
+ );
}
ShapeKind::Circle(text) => {
canvas.draw_circle(
@@ -316,7 +613,14 @@ impl Renderable for Element {
&self.look,
self.properties.clone(),
);
- canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
+ // canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
+ draw_shape_content(
+ text,
+ self.pos.center(),
+ self.pos.size(false),
+ &self.look,
+ canvas,
+ );
}
ShapeKind::DoubleCircle(text) => {
canvas.draw_circle(
@@ -336,7 +640,13 @@ impl Renderable for Element {
&outer_circle_style,
None,
);
- canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
+ draw_shape_content(
+ text,
+ self.pos.center(),
+ self.pos.size(false),
+ &self.look,
+ canvas,
+ );
}
ShapeKind::Connector(label) => {
if debug {
@@ -357,7 +667,14 @@ impl Renderable for Element {
);
}
if let Option::Some(label) = label {
- canvas.draw_text(self.pos.middle(), label, &self.look);
+ // canvas.draw_text(self.pos.middle(), label, &self.look);
+ draw_shape_content(
+ label,
+ self.pos.middle(),
+ self.pos.size(false),
+ &self.look,
+ canvas,
+ );
}
}
}
@@ -378,7 +695,35 @@ impl Renderable for Element {
port: &Option,
) -> (Point, Point) {
match &self.shape {
- ShapeKind::None => (Point::zero(), Point::zero()),
+ ShapeKind::None(x) => {
+ let loc = self.pos.center();
+ let size = self.pos.size(false);
+ // get_connection_point_for_box(loc, size, from, force)
+ match x {
+ ShapeContent::String(_) => {
+ get_connection_point_for_box(loc, size, from, force)
+ }
+ ShapeContent::Html(html) => {
+ let mut loc = self.pos.center();
+ let mut size = self.pos.size(false);
+ if let Option::Some(port_name) = port {
+ let r = get_html_port_location(
+ html,
+ loc,
+ size,
+ &mut Locator {
+ port_name: port_name.to_string(),
+ loc,
+ size,
+ },
+ );
+ loc = r.0;
+ size = r.1;
+ }
+ get_connection_point_for_box(loc, size, from, force)
+ }
+ }
+ }
ShapeKind::Record(rec) => {
let mut loc = self.pos.center();
let mut size = self.pos.size(false);
@@ -398,20 +743,92 @@ impl Renderable for Element {
get_connection_point_for_box(loc, size, from, force)
}
- ShapeKind::Box(_) => {
+ ShapeKind::Box(x) => {
let loc = self.pos.center();
let size = self.pos.size(false);
- get_connection_point_for_box(loc, size, from, force)
+ // get_connection_point_for_box(loc, size, from, force)
+ match x {
+ ShapeContent::String(_) => {
+ get_connection_point_for_box(loc, size, from, force)
+ }
+ ShapeContent::Html(html) => {
+ let mut loc = self.pos.center();
+ let mut size = self.pos.size(false);
+ if let Option::Some(port_name) = port {
+ let r = get_html_port_location(
+ html,
+ loc,
+ size,
+ &mut Locator {
+ port_name: port_name.to_string(),
+ loc,
+ size,
+ },
+ );
+ loc = r.0;
+ size = r.1;
+ }
+ get_connection_point_for_box(loc, size, from, force)
+ }
+ }
}
- ShapeKind::Circle(_) => {
+ ShapeKind::Circle(x) => {
let loc = self.pos.center();
let size = self.pos.size(false);
- get_connection_point_for_circle(loc, size, from, force)
+ // get_connection_point_for_circle(loc, size, from, force)
+ match x {
+ ShapeContent::String(_) => {
+ get_connection_point_for_circle(loc, size, from, force)
+ }
+ ShapeContent::Html(html) => {
+ let mut loc = self.pos.center();
+ let mut size = self.pos.size(false);
+ if let Option::Some(port_name) = port {
+ let r = get_html_port_location(
+ html,
+ loc,
+ size,
+ &mut Locator {
+ port_name: port_name.to_string(),
+ loc,
+ size,
+ },
+ );
+ loc = r.0;
+ size = r.1;
+ }
+ get_connection_point_for_circle(loc, size, from, force)
+ }
+ }
}
- ShapeKind::DoubleCircle(_) => {
+ ShapeKind::DoubleCircle(x) => {
let loc = self.pos.center();
let size = self.pos.size(false);
- get_connection_point_for_circle(loc, size, from, force)
+ // get_connection_point_for_circle(loc, size, from, force)
+ match x {
+ ShapeContent::String(_) => {
+ get_connection_point_for_circle(loc, size, from, force)
+ }
+ ShapeContent::Html(html) => {
+ let mut loc = self.pos.center();
+ let mut size = self.pos.size(false);
+ if let Option::Some(port_name) = port {
+ let r = get_html_port_location(
+ html,
+ loc,
+ size,
+ &mut Locator {
+ port_name: port_name.to_string(),
+ loc,
+ size,
+ },
+ );
+ loc = r.0;
+ size = r.1;
+ }
+ get_connection_point_for_circle(loc, size, from, force)
+ }
+ }
}
_ => {
unreachable!();
diff --git a/layout/src/std_shapes/shapes.rs b/layout/src/std_shapes/shapes.rs
index f159344..5df8253 100644
--- a/layout/src/std_shapes/shapes.rs
+++ b/layout/src/std_shapes/shapes.rs
@@ -7,6 +7,7 @@ use crate::core::base::Orientation;
use crate::core::format::Visible;
use crate::core::geometry::{Point, Position};
use crate::core::style::{LineStyleKind, StyleAttr};
+use crate::gv::html::HtmlGrid;
use crate::std_shapes::render::get_shape_size;
const PADDING: f64 = 60.;
@@ -18,6 +19,12 @@ pub enum LineEndKind {
Arrow,
}
+#[derive(Debug, Clone)]
+pub enum ShapeContent {
+ String(String),
+ Html(HtmlGrid),
+}
+
#[derive(Debug, Clone)]
pub enum RecordDef {
// Label, port:
@@ -37,23 +44,23 @@ impl RecordDef {
#[derive(Debug, Clone)]
pub enum ShapeKind {
- None,
- Box(String),
- Circle(String),
- DoubleCircle(String),
+ None(ShapeContent),
+ Box(ShapeContent),
+ Circle(ShapeContent),
+ DoubleCircle(ShapeContent),
Record(RecordDef),
- Connector(Option),
+ Connector(Option),
}
impl ShapeKind {
pub fn new_box(s: &str) -> Self {
- ShapeKind::Box(s.to_string())
+ ShapeKind::Box(ShapeContent::String(s.to_string()))
}
pub fn new_circle(s: &str) -> Self {
- ShapeKind::Circle(s.to_string())
+ ShapeKind::Circle(ShapeContent::String(s.to_string()))
}
pub fn new_double_circle(s: &str) -> Self {
- ShapeKind::DoubleCircle(s.to_string())
+ ShapeKind::DoubleCircle(ShapeContent::String(s.to_string()))
}
pub fn new_record(r: &RecordDef) -> Self {
ShapeKind::Record(r.clone())
@@ -62,7 +69,7 @@ impl ShapeKind {
if s.is_empty() {
return ShapeKind::Connector(None);
}
- ShapeKind::Connector(Some(s.to_string()))
+ ShapeKind::Connector(Some(ShapeContent::String(s.to_string())))
}
}