1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use lopdf;
use glob_defines::{
    OP_PATH_CONST_MOVE_TO, OP_PATH_CONST_3BEZIER_V1, OP_PATH_CONST_3BEZIER_V2, OP_PATH_CONST_4BEZIER,
    OP_PATH_CONST_LINE_TO, OP_PATH_PAINT_FILL_STROKE_CLOSE_NZ, OP_PATH_PAINT_FILL_NZ,
    OP_PATH_PAINT_STROKE_CLOSE, OP_PATH_PAINT_STROKE, OP_PATH_PAINT_END, OP_PATH_CONST_CLIP_NZ,
};
use Point;
use std::iter::{FromIterator, IntoIterator};

#[derive(Debug, Clone)]
pub struct Line {
    /// 2D Points for the line
    pub points: Vec<(Point, bool)>,
    /// Is the line closed or open?
    pub is_closed: bool,
    /// Should the line be filled (via winding-number rule), for polygons
    pub has_fill: bool,
    /// Should the line have an outline (stroke)?
    pub has_stroke: bool,
    /// Is this line a clipping path?
    pub is_clipping_path: bool,
}

impl Default for Line {
    fn default() -> Self {
        Self {
            points: Vec::new(),
            is_closed: false,
            has_fill: false,
            has_stroke: false,
            is_clipping_path: false,
        }
    }
}

impl FromIterator<(Point, bool)> for Line {
    fn from_iter<I: IntoIterator<Item=(Point, bool)>>(iter: I) -> Self {
        let mut points = Vec::new();
        for i in iter {
            points.push(i);
        }
        Line {
            points: points,
            .. Default::default()
        }
    }
}

impl Line {

    /// Sets if the line is closed or not
    #[inline]
    pub fn set_closed(&mut self, is_closed: bool) {
        self.is_closed = is_closed;
    }

    /// Sets if the line is filled
    #[inline]
    pub fn set_fill(&mut self, has_fill: bool) {
        self.has_fill = has_fill;
    }

    /// Sets if the line is stroked (has an outline)
    #[inline]
    pub fn set_stroke(&mut self, has_stroke: bool) {
        self.has_stroke = has_stroke;
    }

    /// Sets if the line is a clipping path
    #[inline]
    pub fn set_as_clipping_path(&mut self, is_clipping_path: bool) {
        self.is_clipping_path = is_clipping_path;
    }

    pub fn into_stream_op(self)
    -> Vec<lopdf::content::Operation>
    {
        use lopdf::content::Operation;
        let mut operations = Vec::<Operation>::new();

        if self.points.is_empty() { return operations; };

        operations.push(Operation::new(OP_PATH_CONST_MOVE_TO, vec![self.points[0].0.x.into(), self.points[0].0.y.into()]));

        // Skip first element
        let mut current = 1;
        let max_len = self.points.len();

        // Loop over every points, determine if v, y, c or l operation should be used and build
        // curve / line accordingly
        while current < max_len {
            let p1 = &self.points[current - 1];                      // prev pt
            let p2 = &self.points[current];                          // current pt


            if p1.1 && p2.1 {
                // current point is a bezier handle
                // valid bezier curve must have two sequential bezier handles
                // we also can"t build a valid cubic bezier curve if the cuve contains less than
                // four points. If p3 or p4 is marked as "next point is bezier handle" or not, doesn"t matter
                if let Some(p3) = self.points.get(current + 1){
                    if let Some(p4) = self.points.get(current + 2){
                        if p1.0 == p2.0 {
                            // first control point coincides with initial point of curve
                            operations.push(Operation::new(OP_PATH_CONST_3BEZIER_V1, vec![p3.0.x.into(), p3.0.y.into(), p4.0.x.into(), p4.0.y.into()]));
                        }else if p2.0 == p3.0 {
                            // first control point coincides with final point of curve
                            operations.push(Operation::new(OP_PATH_CONST_3BEZIER_V2, vec![p2.0.x.into(), p2.0.y.into(), p4.0.x.into(), p4.0.y.into()]));
                        }else{
                            // regular bezier curve with four points
                            operations.push(Operation::new(OP_PATH_CONST_4BEZIER, vec![p2.0.x.into(), p2.0.y.into(), p3.0.x.into(), p3.0.y.into(), p4.0.x.into(), p4.0.y.into()]));
                        }
                        current += 3;
                        continue;
                    }
                }
            }

            // normal straight line
            operations.push(Operation::new(OP_PATH_CONST_LINE_TO, vec![p2.0.x.into(), p2.0.y.into()]));
            current += 1;
        }

        // how to paint the path
        if self.has_stroke {
            if self.has_fill {
                if self.is_closed {
                    // is filled and stroked and closed
                    operations.push(Operation::new(OP_PATH_PAINT_FILL_STROKE_CLOSE_NZ, vec![]));
                } else {
                    // is filled and stroked but not closed
                    operations.push(Operation::new(OP_PATH_PAINT_FILL_NZ, vec![]));
                }
            } else if self.is_closed {
                // not filled, but stroked and closed
                operations.push(Operation::new(OP_PATH_PAINT_STROKE_CLOSE, vec![]));
            } else {
                // not filled, not closed but only stroked (regular path)
                operations.push(Operation::new(OP_PATH_PAINT_STROKE, vec![]));
            }
        } else if self.has_fill {
            // is not stroked, only filled
            // closed-ness doesn't matter in this case, an area is always closed
            operations.push(Operation::new(OP_PATH_PAINT_FILL_NZ, vec![]));
        } else if self.is_clipping_path {
            // set the path as a clipping path
            operations.push(Operation::new(OP_PATH_CONST_CLIP_NZ, vec![]));
            operations.push(Operation::new(OP_PATH_PAINT_END, vec![]));
        } else {
            // no painting operation nothing, path is invisible, only end the path
            operations.push(Operation::new(OP_PATH_PAINT_END, vec![]));
        }

        operations
    }
}